internal SqlColumn AsSqlColumn() { var sqlColumn = new SqlColumn(); // inheritors can override IsRequired; config might not change the names of Standard Columns SetProperty.IfProvidedNotEmpty <string>(this, "ColumnName", (val) => sqlColumn.ColumnName = val); SetProperty.IfProvidedNotEmpty <string>(this, "DataType", (val) => sqlColumn.SetDataTypeFromConfigString(val)); SetProperty.IfProvided <int>(this, "DataLength", (val) => sqlColumn.DataLength = val); if (sqlColumn.DataLength == 0 && SqlDataTypes.DataLengthRequired.Contains(sqlColumn.DataType)) { throw new ArgumentException($"SQL column {sqlColumn.ColumnName} of data type {sqlColumn.DataType.ToString()} requires a non-zero DataLength property."); } SetProperty.IfProvided <bool>(this, "AllowNull", (val) => sqlColumn.AllowNull = val); SetProperty.IfProvided <bool>(this, "NonClusteredIndex", (val) => sqlColumn.NonClusteredIndex = val); return(sqlColumn); }
/// <summary> /// Create or add to the ColumnOptions object and apply any configuration changes to it. /// </summary> /// <param name="columnOptions">An optional externally-created ColumnOptions object to be updated with additional configuration values.</param> /// <param name="config">A configuration section typically named "columnOptionsSection" (see docs).</param> /// <returns></returns> public static ColumnOptions ConfigureColumnOptions(ColumnOptions columnOptions, IConfigurationSection config) { // Do not use configuration binding (ie GetSection.Get<ColumnOptions>). That will create a new // ColumnOptions object which would overwrite settings if the caller passed in a ColumnOptions // object via the extension method's columnOptions parameter. var opts = columnOptions ?? new ColumnOptions(); if (config == null || !config.GetChildren().Any()) { return(opts); } AddRemoveStandardColumns(); AddAdditionalColumns(); ReadStandardColumns(); ReadMiscColumnOptions(); return(opts); void AddRemoveStandardColumns() { // add standard columns var addStd = config.GetSection("addStandardColumns"); if (addStd.GetChildren().Any()) { foreach (var col in addStd.GetChildren().ToList()) { if (Enum.TryParse(col.Value, ignoreCase: true, result: out StandardColumn stdcol) && !opts.Store.Contains(stdcol)) { opts.Store.Add(stdcol); } } } // remove standard columns var removeStd = config.GetSection("removeStandardColumns"); if (removeStd.GetChildren().Any()) { foreach (var col in removeStd.GetChildren().ToList()) { if (Enum.TryParse(col.Value, ignoreCase: true, result: out StandardColumn stdcol) && opts.Store.Contains(stdcol)) { opts.Store.Remove(stdcol); } } } } void AddAdditionalColumns() { var newcols = config.GetSection("additionalColumns").Get <List <SqlColumn> >() ?? config.GetSection("customColumns").Get <List <SqlColumn> >(); // backwards-compatibility if (newcols != null) { foreach (var c in newcols) { if (!string.IsNullOrWhiteSpace(c.ColumnName)) { if (opts.AdditionalColumns == null) { opts.AdditionalColumns = new Collection <SqlColumn>(); } opts.AdditionalColumns.Add(c); } } } } void ReadStandardColumns() { var section = config.GetSection("id"); if (section != null) { SetCommonColumnOptions(opts.Id); #pragma warning disable 618 // deprecated: BigInt property SetProperty.IfNotNull <bool>(section["bigInt"], (val) => opts.Id.BigInt = val); #pragma warning restore 618 } section = config.GetSection("level"); if (section != null) { SetCommonColumnOptions(opts.Level); SetProperty.IfNotNull <bool>(section["storeAsEnum"], (val) => opts.Level.StoreAsEnum = val); } section = config.GetSection("properties"); if (section != null) { SetCommonColumnOptions(opts.Properties); SetProperty.IfNotNull <bool>(section["excludeAdditionalProperties"], (val) => opts.Properties.ExcludeAdditionalProperties = val); SetProperty.IfNotNull <string>(section["dictionaryElementName"], (val) => opts.Properties.DictionaryElementName = val); SetProperty.IfNotNull <string>(section["itemElementName"], (val) => opts.Properties.ItemElementName = val); SetProperty.IfNotNull <bool>(section["omitDictionaryContainerElement"], (val) => opts.Properties.OmitDictionaryContainerElement = val); SetProperty.IfNotNull <bool>(section["omitSequenceContainerElement"], (val) => opts.Properties.OmitSequenceContainerElement = val); SetProperty.IfNotNull <bool>(section["omitStructureContainerElement"], (val) => opts.Properties.OmitStructureContainerElement = val); SetProperty.IfNotNull <bool>(section["omitElementIfEmpty"], (val) => opts.Properties.OmitElementIfEmpty = val); SetProperty.IfNotNull <string>(section["propertyElementName"], (val) => opts.Properties.PropertyElementName = val); SetProperty.IfNotNull <string>(section["rootElementName"], (val) => opts.Properties.RootElementName = val); SetProperty.IfNotNull <string>(section["sequenceElementName"], (val) => opts.Properties.SequenceElementName = val); SetProperty.IfNotNull <string>(section["structureElementName"], (val) => opts.Properties.StructureElementName = val); SetProperty.IfNotNull <bool>(section["usePropertyKeyAsElementName"], (val) => opts.Properties.UsePropertyKeyAsElementName = val); // TODO PropertiesFilter would need a compiled Predicate<string> (high Roslyn overhead, see Serilog Config repo #106) } section = config.GetSection("timeStamp"); if (section != null) { SetCommonColumnOptions(opts.TimeStamp); SetProperty.IfNotNull <bool>(section["convertToUtc"], (val) => opts.TimeStamp.ConvertToUtc = val); } section = config.GetSection("logEvent"); if (section != null) { SetCommonColumnOptions(opts.LogEvent); SetProperty.IfNotNull <bool>(section["excludeAdditionalProperties"], (val) => opts.LogEvent.ExcludeAdditionalProperties = val); SetProperty.IfNotNull <bool>(section["ExcludeStandardColumns"], (val) => opts.LogEvent.ExcludeStandardColumns = val); } section = config.GetSection("message"); if (section != null) { SetCommonColumnOptions(opts.Message); } section = config.GetSection("exception"); if (section != null) { SetCommonColumnOptions(opts.Exception); } section = config.GetSection("messageTemplate"); if (section != null) { SetCommonColumnOptions(opts.MessageTemplate); } // Standard Columns are subclasses of the SqlColumn class void SetCommonColumnOptions(SqlColumn target) { SetProperty.IfNotNullOrEmpty <string>(section["columnName"], (val) => target.ColumnName = val); SetProperty.IfNotNull <string>(section["dataType"], (val) => target.SetDataTypeFromConfigString(val)); SetProperty.IfNotNull <bool>(section["allowNull"], (val) => target.AllowNull = val); SetProperty.IfNotNull <int>(section["dataLength"], (val) => target.DataLength = val); SetProperty.IfNotNull <bool>(section["nonClusteredIndex"], (val) => target.NonClusteredIndex = val); } } void ReadMiscColumnOptions() { SetProperty.IfNotNull <bool>(config["disableTriggers"], (val) => opts.DisableTriggers = val); SetProperty.IfNotNull <bool>(config["clusteredColumnstoreIndex"], (val) => opts.ClusteredColumnstoreIndex = val); string pkName = config["primaryKeyColumnName"]; if (!string.IsNullOrEmpty(pkName)) { if (opts.ClusteredColumnstoreIndex) { throw new ArgumentException("SQL Clustered Columnstore Indexes and primary key constraints are mutually exclusive."); } foreach (var standardCol in opts.Store) { var stdColOpts = opts.GetStandardColumnOptions(standardCol); if (pkName.Equals(stdColOpts.ColumnName, StringComparison.InvariantCultureIgnoreCase)) { opts.PrimaryKey = stdColOpts; break; } } if (opts.PrimaryKey == null && opts.AdditionalColumns != null) { foreach (var col in opts.AdditionalColumns) { if (pkName.Equals(col.ColumnName, StringComparison.InvariantCultureIgnoreCase)) { opts.PrimaryKey = col; break; } } } if (opts.PrimaryKey == null) { throw new ArgumentException($"Could not match the configured primary key column name \"{pkName}\" with a data column in the table."); } } } }
/// <summary> /// Populate ColumnOptions properties and collections from app config /// </summary> internal static ColumnOptions ConfigureColumnOptions(MSSqlServerConfigurationSection config, ColumnOptions columnOptions) { var opts = columnOptions ?? new ColumnOptions(); AddRmoveStandardColumns(); AddAdditionalColumns(); ReadStandardColumns(); ReadMiscColumnOptions(); return(opts); void AddRmoveStandardColumns() { // add standard columns if (config.AddStandardColumns.Count > 0) { foreach (StandardColumnConfig col in config.AddStandardColumns) { if (Enum.TryParse(col.Name, ignoreCase: true, result: out StandardColumn stdcol) && !opts.Store.Contains(stdcol)) { opts.Store.Add(stdcol); } } } // remove standard columns if (config.RemoveStandardColumns.Count > 0) { foreach (StandardColumnConfig col in config.RemoveStandardColumns) { if (Enum.TryParse(col.Name, ignoreCase: true, result: out StandardColumn stdcol) && opts.Store.Contains(stdcol)) { opts.Store.Remove(stdcol); } } } } void AddAdditionalColumns() { if (config.Columns.Count > 0) { foreach (ColumnConfig c in config.Columns) { if (!string.IsNullOrWhiteSpace(c.ColumnName)) { if (opts.AdditionalColumns == null) { opts.AdditionalColumns = new Collection <SqlColumn>(); } opts.AdditionalColumns.Add(c.AsSqlColumn()); } } } } void ReadStandardColumns() { SetCommonColumnOptions(config.Exception, opts.Exception); SetCommonColumnOptions(config.Id, opts.Id); SetCommonColumnOptions(config.Level, opts.Level); SetCommonColumnOptions(config.LogEvent, opts.LogEvent); SetCommonColumnOptions(config.Message, opts.Message); SetCommonColumnOptions(config.MessageTemplate, opts.MessageTemplate); SetCommonColumnOptions(config.PropertiesColumn, opts.Properties); SetCommonColumnOptions(config.TimeStamp, opts.TimeStamp); SetProperty.IfProvided <bool>(config.Level, "StoreAsEnum", (val) => opts.Level.StoreAsEnum = val); SetProperty.IfProvided <bool>(config.LogEvent, "ExcludeStandardColumns", (val) => opts.LogEvent.ExcludeStandardColumns = val); SetProperty.IfProvided <bool>(config.LogEvent, "ExcludeAdditionalProperties", (val) => opts.LogEvent.ExcludeAdditionalProperties = val); SetProperty.IfProvided <bool>(config.PropertiesColumn, "ExcludeAdditionalProperties", (val) => opts.Properties.ExcludeAdditionalProperties = val); SetProperty.IfProvided <string>(config.PropertiesColumn, "DictionaryElementName", (val) => opts.Properties.DictionaryElementName = val); SetProperty.IfProvided <string>(config.PropertiesColumn, "ItemElementName", (val) => opts.Properties.ItemElementName = val); SetProperty.IfProvided <bool>(config.PropertiesColumn, "OmitDictionaryContainerElement", (val) => opts.Properties.OmitDictionaryContainerElement = val); SetProperty.IfProvided <bool>(config.PropertiesColumn, "OmitSequenceContainerElement", (val) => opts.Properties.OmitSequenceContainerElement = val); SetProperty.IfProvided <bool>(config.PropertiesColumn, "OmitStructureContainerElement", (val) => opts.Properties.OmitStructureContainerElement = val); SetProperty.IfProvided <bool>(config.PropertiesColumn, "OmitElementIfEmpty", (val) => opts.Properties.OmitElementIfEmpty = val); SetProperty.IfProvided <string>(config.PropertiesColumn, "PropertyElementName", (val) => opts.Properties.PropertyElementName = val); SetProperty.IfProvided <string>(config.PropertiesColumn, "RootElementName", (val) => opts.Properties.RootElementName = val); SetProperty.IfProvided <string>(config.PropertiesColumn, "SequenceElementName", (val) => opts.Properties.SequenceElementName = val); SetProperty.IfProvided <string>(config.PropertiesColumn, "StructureElementName", (val) => opts.Properties.StructureElementName = val); SetProperty.IfProvided <bool>(config.PropertiesColumn, "UsePropertyKeyAsElementName", (val) => opts.Properties.UsePropertyKeyAsElementName = val); SetProperty.IfProvided <bool>(config.TimeStamp, "ConvertToUtc", (val) => opts.TimeStamp.ConvertToUtc = val); // Standard Columns are subclasses of the SqlColumn class void SetCommonColumnOptions(ColumnConfig source, SqlColumn target) { SetProperty.IfProvidedNotEmpty <string>(source, "ColumnName", (val) => target.ColumnName = val); SetProperty.IfProvided <string>(source, "DataType", (val) => target.SetDataTypeFromConfigString(val)); SetProperty.IfProvided <bool>(source, "AllowNull", (val) => target.AllowNull = val); SetProperty.IfProvided <int>(source, "DataLength", (val) => target.DataLength = val); SetProperty.IfProvided <bool>(source, "NonClusteredIndex", (val) => target.NonClusteredIndex = val); } } void ReadMiscColumnOptions() { SetProperty.IfProvided <bool>(config, "DisableTriggers", (val) => opts.DisableTriggers = val); SetProperty.IfProvided <bool>(config, "ClusteredColumnstoreIndex", (val) => opts.ClusteredColumnstoreIndex = val); string pkName = null; SetProperty.IfProvidedNotEmpty <string>(config, "PrimaryKeyColumnName", (val) => pkName = val); if (pkName != null) { if (opts.ClusteredColumnstoreIndex) { throw new ArgumentException("SQL Clustered Columnstore Indexes and primary key constraints are mutually exclusive."); } foreach (var standardCol in opts.Store) { var stdColOpts = opts.GetStandardColumnOptions(standardCol); if (pkName.Equals(stdColOpts.ColumnName, StringComparison.InvariantCultureIgnoreCase)) { opts.PrimaryKey = stdColOpts; break; } } if (opts.PrimaryKey == null && opts.AdditionalColumns != null) { foreach (var col in opts.AdditionalColumns) { if (pkName.Equals(col.ColumnName, StringComparison.InvariantCultureIgnoreCase)) { opts.PrimaryKey = col; break; } } } if (opts.PrimaryKey == null) { throw new ArgumentException($"Could not match the configured primary key column name \"{pkName}\" with a data column in the table."); } } } }