locked
How do you stop a reverse databind from DataGrid to the Entity Framework RRS feed

  • Question

  • I am having a problem with my WPF 4 Datagrid attempting to write invalid data back to an Entity Framework context.  Take this simple example, I have a Person object bound to a datagrid: FirstName and LastName, neither can be null.  My Person entity implements IDataErrorInfo to return errors if either of those is null.  When I click in a new row in the datagrid to create a new person entry, the row calls the Error properties and knows that initially, that data is bad.  But if I click to go to another row in the grid, without filling in both values first, it will attempt to write the data back to the EF context (which throws an exception) even though it knows the data is bad.

    Is there a way for me to prevent the DataGrid from attempting to write the bad data back?  Another interface I have to implement?

    Thanks...

    public partial class Person : IDataErrorInfo
    {
    	// public string FirstName implemented by EF
    	// public string LastName implemented by EF
    
    	public string Error
    	{
    		get
    		{
    			StringBuilder error = new StringBuilder();
    
    			PropertyDescriptorCollection props = TypeDescriptor.GetProperties(this);
    			foreach (PropertyDescriptor prop in props)
    			{
    				string propertyError = this[prop.Name];
    				if (propertyError != string.Empty)
    				{
    					error.Append((error.Length != 0 ? ", " : "") + propertyError);
    				}
    			}
    
    			return error.ToString();
    		}
    	}
    
    	public string this[string columnName]
    	{
    		get
    		{
    			if (columnName == "FirstName" && string.IsNullOrWhiteSpace(FirstName))
    				return "First Name cannot be blank.";
    
    			if (columnName == "LastName" && string.IsNullOrWhiteSpace(LastName))
    				return "Last Name cannot be blank.";
    
    			return "";
    		}
    	}
    }
    
    <DataGrid AutoGenerateColumns="false" Name="personGrid" ItemsSource="{Binding Path=People}">
      <DataGrid.Resources>
        <Style x:Key="errorStyle" TargetType="{x:Type TextBox}">
          <Setter Property="Padding" Value="-2"/>
          <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="True">
              <Setter Property="Background" Value="LightCoral"/>
              <Setter Property="ToolTip" 
                 Value="{Binding RelativeSource={RelativeSource Self},
                 Path=(Validation.Errors)[0].ErrorContent}"/>
            </Trigger>
          </Style.Triggers>
        </Style>
        <Style x:Key="RowStyle" TargetType="{x:Type DataGridRow}">
          <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="true">
              <Setter Property="BorderThickness" Value="1"/>
              <Setter Property="BorderBrush" Value="Red"/>
              <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, 
                        Path=(Validation.Errors)[0].ErrorContent}"/>
            </Trigger>
          </Style.Triggers>
        </Style>
      </DataGrid.Resources>
      <DataGrid.RowValidationRules>
        <local:RowDataInfoValidationRule ValidationStep="UpdatedValue" />
      </DataGrid.RowValidationRules>
      <DataGrid.Columns>
        <DataGridTextColumn x:Name="FirstNameColumn" Width="2*" Header="First Name" EditingElementStyle="{StaticResource errorStyle}">
          <DataGridTextColumn.Binding>
            <Binding Path="FirstName" ValidatesOnDataErrors="True" UpdateSourceTrigger="LostFocus">
              <Binding.ValidationRules>
                <local:CellDataInfoValidationRule ValidationStep="UpdatedValue"/>
              </Binding.ValidationRules>
            </Binding>
          </DataGridTextColumn.Binding>
        </DataGridTextColumn>
        <DataGridTextColumn x:Name="LastNameColumn" Width="4*" Header="Last Name" EditingElementStyle="{StaticResource errorStyle}">
          <DataGridTextColumn.Binding>
            <Binding Path="LastName" ValidatesOnDataErrors="True" UpdateSourceTrigger="LostFocus">
              <Binding.ValidationRules>
                <local:CellDataInfoValidationRule ValidationStep="UpdatedValue"/>
              </Binding.ValidationRules>
            </Binding>
          </DataGridTextColumn.Binding>
        </DataGridTextColumn>
      </DataGrid.Columns>
    </DataGrid>
    
    The RowDataInfoValidationRule class and the CellDataInfoValidationRule class are pretty standard, inheriting from ValidationRule and looking at the IDataErrorInfo properties of the passed in entity object.  I can include those classes if needed.
    Thursday, January 20, 2011 8:13 PM

Answers

  • Hi,

    if you use IDataErrorInfo to validate input, it means that the input has to be written back to the data object in order to be validated, right? Since you don't want that, you need to validate the data before it is sent to the data object. What you can do is to provide a ValidationRule which checks for empty values, with a ValidationStep setting of RawProposedValue or ConvertedProposedValue (or so). The ValidationRule will see the value before it is written back to the binding source, and if it returns false, the source will not be updated.


    http://wpfglue.wordpress.com
    • Proposed as answer by Jie Bao Wednesday, January 26, 2011 10:28 AM
    • Marked as answer by Douglas Singel Wednesday, January 26, 2011 1:58 PM
    Monday, January 24, 2011 10:24 PM
  • Hi Douglas Singel,

    Thanks hbarck, I think hbarck answer your original question again and tell us how to validate the value before updating the bound instances.

    Regarding to the setter of the entity instance, the cause of it is same with the explanation from hbarck, data is updated into the property and then do the validation, so an exception will be thrown from setter "the property does support null value". So a better way for it is to handle the validation by rule as hbarck said. Or add some code in the setter to check the null value and return some valuable message to notify the UI.

    Hope this helps.

    Sincerely,


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    • Marked as answer by Douglas Singel Wednesday, January 26, 2011 1:58 PM
    Wednesday, January 26, 2011 10:42 AM

All replies

  • Hi Douglas Singel,

    Do you mean you want to block the exception when user add a new row in the DataGrid? Please soecify the Binding.ValidatesOnExceptions property to true to include the ExceptionValidationRule  for the binding.

    On the other hand, I recommend you to implement a default value for the new row. How to? Set the value of the properties of the Person in the constructor.

    Sincerely,


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Friday, January 21, 2011 5:48 AM
  • Hi Bob,

    Thanks for the response.  I was more hoping that the DataGrid would not even attempt to write back to the source entity.  I can default a value in the constructor, but that does not prevent the user from blanking out that default value and then switching rows in the datagrid which produces the exception.  This problem can occur on either an "insert" or an "update".

    I can work with the ValidatedOnExceptions but there seems to be a discrepancy in behavior.

    By comparison, my entity class also has an age property: int? age (allows null).  If I display this in the datagrid and the user attempts to blank out the value, the datagrid never tries to write any value back to the source entity.  I.e.  The age setter is never called.  But the same doesn't happen with the name properties.  The setter properties are called there.  Why is the behavior different and how can I make the name properties behave like the age?

    Friday, January 21, 2011 1:37 PM
  • Hi Douglas,

    I tested a simple sample with the entity framework, yes, it seems the code did not call the setter of a Nullable property in the entity. the property was null in the new record.

    What is the Name property type, I have one same Name prioperty, it is string, and if we do not specify the value in the added new row, the setter will not be called also. I think it is a behavioure of entity class, for the default value of one property, it will not call the setter method. (I will consult it).

    Regarding to the validation in the new row, two options properties we can set on the binding expression:

    1) TargetNullValue for displaying a value when the source is null and 2) FallbackValue for displaying a value when the source cannot return a value.

    Sincerely,


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Monday, January 24, 2011 11:59 AM
  • The Name property is a string that does not allow nulls.  The database is setup to not allow nulls for that field so the code that the EF generates enforces this.  The generated code is below:

    /// <summary>
        /// No Metadata Documentation available.
        /// </summary>
        [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
        [DataMemberAttribute()]
        public global::System.String Name
        {
          get
          {
            return _Name;
          }
          private set
          {
            OnNameChanging(value);
            ReportPropertyChanging("Name");
            _Name = StructuralObject.SetValidValue(value, false);
            ReportPropertyChanged("Name");
            OnNameChanged();
          }
        }
        private global::System.String _Name;
    
    The line "Name = StructuralObject.SetValidValue(value, false);" throws the exception since it is enforcing the non-null rule.  I could change this code and enforce it myself in other ways that won't throw an exception but everytime the model is regenerated, I have to remember to fix this (not really preferred).  Just hoping there is a better way.
    Monday, January 24, 2011 2:53 PM
  • Hi,

    if you use IDataErrorInfo to validate input, it means that the input has to be written back to the data object in order to be validated, right? Since you don't want that, you need to validate the data before it is sent to the data object. What you can do is to provide a ValidationRule which checks for empty values, with a ValidationStep setting of RawProposedValue or ConvertedProposedValue (or so). The ValidationRule will see the value before it is written back to the binding source, and if it returns false, the source will not be updated.


    http://wpfglue.wordpress.com
    • Proposed as answer by Jie Bao Wednesday, January 26, 2011 10:28 AM
    • Marked as answer by Douglas Singel Wednesday, January 26, 2011 1:58 PM
    Monday, January 24, 2011 10:24 PM
  • Hi Douglas Singel,

    Thanks hbarck, I think hbarck answer your original question again and tell us how to validate the value before updating the bound instances.

    Regarding to the setter of the entity instance, the cause of it is same with the explanation from hbarck, data is updated into the property and then do the validation, so an exception will be thrown from setter "the property does support null value". So a better way for it is to handle the validation by rule as hbarck said. Or add some code in the setter to check the null value and return some valuable message to notify the UI.

    Hope this helps.

    Sincerely,


    Bob Bao [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    • Marked as answer by Douglas Singel Wednesday, January 26, 2011 1:58 PM
    Wednesday, January 26, 2011 10:42 AM
  • I am going with Hbarck's as the best since it gives me the best options of dealing with the problem, though it doesn't explain everything that I am seeing.  The biggest unexplained questions are why the DataGrid attempts to write back values for a string (nullable or not) but it does not attempt to write back bad values for a int (nullable or not).

    Bob, I am prefer not to add code to the setter of the property since it is autogenerated code from the Entity Framework.  Every model update would overwrite my changes.

    Wednesday, January 26, 2011 2:03 PM