I have seen this question already but it does not explain it so that I understand what is actually going on. I’ve been developing for years and never come across this before (though my usage of Linq and Parallel is fairly recent).
My code is:
Parallel.ForEach(databaseMetadata.Rows.Cast<DataRow>(), row => {
var fieldName = row.Item("Name", "");
var field = this.Fields.Where(f => f.Name.ToLower() == fieldName.ToLower()).SingleOrDefault();
if(field != null) { field.Validate(this, connection, row); }
});
Within the field.Validate function it sets a property named ‘HasBeenValidated’ on the field object to true, however, as soon as I come out of this Parallel.ForEach loop, that property is set back to false. Can someone please explain why this is happening and what I can do to ensure that changes within the loops are persisted outside of the loop.
Edited:
Below is a copy of the code in field.Validate:
internal void Validate(EntityAttribute entity, SqlConnection connection, [AllowNull] DataRow metadata) {
this.HasBeenValidated = true;
var isRequired = this.IsRequired;
var maxLength = this.MaxLength;
var isAutoGenerated = this.IsAutoGenerated;
var dataType = this.member.PropertyType;
var dataTypeAsString = "";
if(metadata != null) {
isRequired = metadata.Item("IsRequired", false);
maxLength = metadata.Item("MaxLength", 0);
isAutoGenerated = metadata.Item("IsAutoGenerated", false);
dataTypeAsString = metadata.Item("DataType", "");
if(dataTypeAsString == this.member.PropertyType.ToSqlServerDataType()) { dataTypeAsString = ""; }
} else {
dataTypeAsString = this.member.PropertyType.ToSqlServerDataType();
}
if(metadata == null || isRequired != this.IsRequired || maxLength != this.MaxLength || isAutoGenerated != this.IsAutoGenerated || dataTypeAsString != "") {
var sql = string.Format((metadata == null ? "ALTER TABLE [{0}].[{1}] ADD" : "ALTER TABLE {0} ALTER COLUMN"), entity.Schema, entity.Name) + " " + this.Sql + ";";
if(!connection.ExecuteCommand(sql, 1)) {
throw new InvalidOperationException("Unable to create or alter column '" + this.Name + "' on table '" + entity.Name + "'.");
}
}
}
The HasBeenValidated property is defined on the field object as:
internal bool HasBeenValidated { get; set; }
Thanks in advance.
Apologies if I’ve wasted anyone’s time but I have figured out the cause of this issue. The this.Fields list is an IEnumerable<> of the field type which is queryable. I thought that this would be better than having a greedy list (since this class has a good number of lists on it). The code for generating the Fields list is:
What I completely failed to realise was that GetCustomAttributes, rather unexpectedly (from my point of view anyway) regenerates a copy of the attribute class each time it is called.
Had this been a simpler class I might have suspected this sooner, but I was also changing properties in the fields class when setting the Member property (i.e. extracting metadata from the info.Property and setting properties within the field class based on the properties of that class) so when I was looking at the field class in the debugger I could see a lot of the properties had been changed (which mislead me to think it was the same instance of the field class and not a copy).
I really apologise if I’ve wasted anyone’s time and effort in this but hopefully by posting my mistake this can help other people in future who might stumble using the GetCustomAttribute in a similar way inside a non-greedy Linq expression.