none
Code First and BindingSource RRS feed

  • Question

  • In my application I have two entities with multiple relationships.

    public class Post
        {
            public int Id { get; set; }
            public string Title { get; set; }
            public virtual Person CreatedBy { get; set; }
            public virtual Person UpdatedBy { get; set; }
        }
    
        public class Person
        {
            public int Id { get; set; }
            public string Name { get; set; }
            [InverseProperty("CreatedBy")]
            public ICollection<Post> PostsWritten { get; set; }
            [InverseProperty("UpdatedBy")]
            public ICollection<Post> PostsUpdated { get; set; }
        }
    
        public class MyContext : DbContext
        {
            public DbSet<Post> Posts { get; set; }
            public DbSet<Person> People { get; set; }
        }

    By using the Data Source on a WinForms, with just one BindingSource I placed all TextBox. ID and Title for the main entity ID and Name for the "CreatedBy" and another ID and Name for the entity "UpdatedBy". In total 6 TextBox.

    Using Lazy Loading I get both the main entity (Post) that the related entities.

    private void button1_Click(object sender, EventArgs e)
            {
                using (var ctx = new MyContext())
                {
    
                    var a = (from d in ctx.Posts
                             where d.Id == 1
                             select d).Single();
    
                    postBindingSource.DataSource = a;
                }
            }

    My question is this: why if and only if, CreatedBy and UpdatedBy have equal and identical key, when I try to edit the text (obtained using Lazy Loading) in the "Name" TextBox UpdatedBy, automatically writes to me the same edited text in "Name" TextBox UpdatedBy?

    But if, for example, is equal to 1 CreatedBy and UpdatedBy equals 2, this strangeness does not happen!

    Maybe the BindingSource's fault?
    Saturday, June 15, 2013 5:03 PM

Answers

  • I think I foundthe solution. I made ​​some changes to yourcode, but I need to know what you think. I have used again the datasource so as to useonly one bindigsource, but eliminating through the properties of DataBindings to CreatedBy and UpdatedBy.

    public partial class Form1 : Form
        {
            Post post;
    
            public Form1()
            {
                InitializeComponent();
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                using (var ctx = new MyContext())
                {
                    ctx.Configuration.LazyLoadingEnabled = false;
    
                    post = (from d in ctx.Posts
                                .Include("CreatedBy")
                                .Include("UpdatedBy")
                            where d.Id == 1
                            select d).Single();
    
                    bs.DataSource = post;
    
                    tbCreatedBy_Name.Text = post.CreatedBy.Name.ToString();
                    tbUpdatedBy_Name.Text = post.UpdatedBy.Name.ToString();
                }
            }
    
            private void tbCreatedBy_Validating(object sender, CancelEventArgs e)
            {
                int id;
                TextBox tb = (TextBox)sender;
                if (!string.IsNullOrEmpty(tb.Text) && !int.TryParse(tb.Text, out id))
                {
                    e.Cancel = true;
                }
            }
    
            private void tbuUpdatedBy_Validating(object sender, CancelEventArgs e)
            {
                int id;
                TextBox tb = (TextBox)sender;
                if (!string.IsNullOrEmpty(tb.Text) && !int.TryParse(tb.Text, out id))
                {
                    e.Cancel = true;
                }
            }
    
            private void tbCreatedBy_Validated(object sender, EventArgs e)
            {
                using (var ctx = new MyContext())
                {
                    int id;
                    TextBox tb = (TextBox)sender;
                    if (int.TryParse(tb.Text, out id))
                    {
                        Person person = ctx.People.Find(id);
                        if (person != null)
                        {
                            post.CreatedBy = person;
                            tbCreatedBy_Name.Text = post.CreatedBy.Name.ToString();
                        }
                        else
                        {
                            Person p = new Person();
                            post.CreatedBy = p;
                            tbCreatedBy_Name.Clear();
                        }
                    }
                }
            }
    
            private void tbuUpdatedBy_Validated(object sender, EventArgs e)
            {
                using (var ctx = new MyContext())
                {
                    int id;
                    TextBox tb = (TextBox)sender;
                    if (int.TryParse(tb.Text, out id))
                    {
                        Person person = ctx.People.Find(id);
                        if (person != null)
                        {
                            post.UpdatedBy = person;
                            tbUpdatedBy_Name.Text = post.UpdatedBy.Name.ToString();
                        }
                        else
                        {
                            Person p = new Person();
                            post.UpdatedBy = p;
                            tbUpdatedBy_Name.Clear();
                        }
                    }
                }
            }
        }
    What do you think, or what would you improve?
    Actually I think my problem was due to a failure to initialize CreatedBy and UpdatedBy. But I think I've definitely resolved.
    However I am surprised you haven't found similar solutions around the net or in books.

    I guess now I will attempt to bring the code in WPF.

    Waiting for your latest positive feedback.
    P.S.: sorry for my bad English.
     

    • Edited by piedatt80 Monday, July 15, 2013 9:30 AM Error
    • Marked as answer by piedatt80 Friday, July 18, 2014 3:27 PM
    Thursday, July 11, 2013 3:43 PM

All replies

  • Hi piedatt80,

    Yes, I think that's caused by BindingSource.

    If we use data bindings, what we modified in the TextBox is mapped to the instance of Person. If the CreatedBy and the UpdatedBy are the same, the text boxes are binding to the same instance of Person. So if you modified one textbox, the other one will also be updated.

    The fact is we are setting Id and Name for the Person, not the CreatedBy and UpdatedBy properties of the post.

    Best regards,


    Chester Hong
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Monday, June 17, 2013 8:53 AM
    Moderator
  • Thanks for the reply. However I get the same result even with a WPF application without the bindingsource.

    <Window
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Wpf" mc:Ignorable="d" x:Class="Wpf.MainWindow"
            Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
        <Window.Resources>
            <CollectionViewSource x:Key="postViewSource" d:DesignSource="{d:DesignInstance {x:Type local:Post}, CreateList=True}"/>
        </Window.Resources>
        <Grid>
    
            <Grid x:Name="grid1" VerticalAlignment="Top" Margin="166,57,0,0" HorizontalAlignment="Left" DataContext="{StaticResource postViewSource}">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                <Label VerticalAlignment="Center" Grid.Row="0" Margin="3" HorizontalAlignment="Left" Grid.Column="0" Content="Id:"/>
                <TextBox x:Name="idTextBox" Width="120" VerticalAlignment="Center" Text="{Binding Id, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" Grid.Row="0" Margin="3" Height="23" HorizontalAlignment="Left" Grid.Column="1"/>
                <Label VerticalAlignment="Center" Grid.Row="1" Margin="3" HorizontalAlignment="Left" Grid.Column="0" Content="Title:"/>
                <TextBox x:Name="titleTextBox" Width="120" VerticalAlignment="Center" Text="{Binding Title, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" Grid.Row="1" Margin="3" Height="23" HorizontalAlignment="Left" Grid.Column="1"/>
                <Label VerticalAlignment="Center" Grid.Row="2" Margin="3" HorizontalAlignment="Left" Grid.Column="0" Content="Id:"/>
                <TextBox x:Name="idTextBox1" Width="120" VerticalAlignment="Center" Text="{Binding CreatedBy.Id, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" Grid.Row="2" Margin="3" Height="23" HorizontalAlignment="Left" Grid.Column="1"/>
                <Label VerticalAlignment="Center" Grid.Row="3" Margin="3" HorizontalAlignment="Left" Grid.Column="0" Content="Name:"/>
                <TextBox x:Name="nameTextBox" Width="120" VerticalAlignment="Center" Text="{Binding CreatedBy.Name, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" Grid.Row="3" Margin="3" Height="23" HorizontalAlignment="Left" Grid.Column="1"/>
                <Label VerticalAlignment="Center" Grid.Row="4" Margin="3" HorizontalAlignment="Left" Grid.Column="0" Content="Id:"/>
                <TextBox x:Name="idTextBox2" Width="120" VerticalAlignment="Center" Text="{Binding UpdatedBy.Id, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" Grid.Row="4" Margin="3" Height="23" HorizontalAlignment="Left" Grid.Column="1"/>
                <Label VerticalAlignment="Center" Grid.Row="5" Margin="3" HorizontalAlignment="Left" Grid.Column="0" Content="Name:"/>
                <TextBox x:Name="nameTextBox1" Width="120" VerticalAlignment="Center" Text="{Binding UpdatedBy.Name, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" Grid.Row="5" Margin="3" Height="23" HorizontalAlignment="Left" Grid.Column="1"/>
            </Grid>
            <Button Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="10,10,0,0" Click="Button_Click"/>
    
        </Grid>
    </Window>
    private void Button_Click(object sender, RoutedEventArgs e)
            {
                using (var ctx = new MyContext())
                {
                    var a = (from d in ctx.Posts
                                where d.Id == 1
                                select d).Single();
    
                    grid1.DataContext = a;
                }
            }
    public partial class DB : DbMigration
        {
            public override void Up()
            {
                CreateTable(
                    "dbo.Posts",
                    c => new
                        {
                            Id = c.Int(nullable: false, identity: true),
                            Title = c.String(),
                            CreatedBy_Id = c.Int(),
                            UpdatedBy_Id = c.Int(),
                        })
                    .PrimaryKey(t => t.Id)
                    .ForeignKey("dbo.People", t => t.CreatedBy_Id)
                    .ForeignKey("dbo.People", t => t.UpdatedBy_Id)
                    .Index(t => t.CreatedBy_Id)
                    .Index(t => t.UpdatedBy_Id);
                
                CreateTable(
                    "dbo.People",
                    c => new
                        {
                            Id = c.Int(nullable: false, identity: true),
                            Name = c.String(),
                        })
                    .PrimaryKey(t => t.Id);
                
            }
    How can I solve my problem? Where can I find an example to study?


    Saturday, June 22, 2013 8:59 AM
  • Hi piedatt80,

    I think no matter whether you are using WinForms BindingSource or WPF data bindings, they are binding to the same Person if the id are the same. The navigation property keeps a reference to Person, so what you have modified is the same person.

    The MSDN samples are very simple and there isn't an entity contains two navigation properties to the same other entity.

    http://msdn.microsoft.com/en-us/data/jj682076

    http://msdn.microsoft.com/en-us/data/jj574514

    If you want to modify only one person without changing the other one, I think you need to write some code to add another Person into the DbSet<Person> and then set the navigation property to the newly added Person.

    Best regards,


    Chester Hong
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help

    Tuesday, June 25, 2013 5:24 AM
    Moderator
  • I made the following change to my code:

    public class Post
        {
            public int Id { get; set; }
            public string Title { get; set; }
            public virtual PersonA CreatedBy { get; set; }
            public virtual PersonB UpdatedBy { get; set; }
        }
    
        public class PersonA
        {
            public int Id { get; set; }
            public string Name { get; set; }
            [InverseProperty("CreatedBy")]
            public ICollection<Post> PostsWritten { get; set; }
        }
    
        public class PersonB
        {
            public int Id { get; set; }
            public string Name { get; set; }
            [InverseProperty("UpdatedBy")]
            public ICollection<Post> PostsUpdated { get; set; }
        }
    
        public class MyContext : DbContext
        {
            public DbSet<Post> Posts { get; set; }
            public DbSet<PersonA> PeopleA { get; set; }
            public DbSet<PersonB> PeopleB { get; set; }
        }
    now the application works properly, but contrary to what I wanted, I have created two Person tables. PersonA and PersonB. This is not my goal, but to have multiple relationships with only one Person table.
    I'm studying as suggested by Julia Lerman's book "Code First" in "Working with Inverse Navigation Properties" but I can't find a good solution.
    Where I am wrong?


    • Edited by piedatt80 Thursday, June 27, 2013 10:08 AM error
    Thursday, June 27, 2013 10:07 AM
  • Hi piedatt80,

    The solution works because you are binding to two different tables. The CreatedBy and UpdatedBy can never be the same.

    If you want to use only one Person table, I think you need to implement some code as I mentioned before.

    For example, try don't use data binding to bind the Person's Id. Add an Validated event handler to track if Id is changed instead. And then set the related navigation property and reset the data binding to other textboxes.

    Best regards,


    Chester Hong
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help

    Tuesday, July 2, 2013 5:33 AM
    Moderator
  • I made several attempts, but all without success.

    However I can't find good examples, I don't think I was the only one facing this problem.

    A few posts above you wrote: "If you want to modify only one person without changing the other one, I think you need to write some code to add another Person into the DbSet<Person> and then set the navigation property to the newly added Person."

    Can I see some examples? I would be a great help.

    Thank you.

    Saturday, July 6, 2013 7:46 AM
  • Hi piedatt80,

    For example, I add data binding for the textbox in Form's Load event. I didn't data binding the Id for CreatedBy and UpdatedBy.

    post = (from p in context.Posts where p.Id == 1 select p).Single();
    bs.DataSource = post;
    
    tbId.DataBindings.Add("Text", bs, "Id");
    tbTitle.DataBindings.Add("Text", bs, "Title");
    bsCreatedBy = new BindingSource(bs, "CreatedBy");
    //tbCreatedById.DataBindings.Add("Text", bsCreatedBy , "Id");
    tbCreatedById.Text = post.CreatedBy.Id.ToString();
    tbCreatedByName.DataBindings.Add("Text", bsCreatedBy , "Name");
    bsUpdatedBy = new BindingSource(bs, "UpdatedBy");
    //tbUpdatedById.DataBindings.Add("Text", bsUpdatedBy , "Id");
    tbUpdatedById.Text = post.UpdatedBy.Id.ToString();
    tbUpdatedByName.DataBindings.Add("Text", bsUpdatedBy , "Name");

    I use Validating and Validated events to check whether the id in valid and whether there is already an entity with the same id or not. If there is the entity with the same id, I set post.CreatedBy to the existing person. If not, I will create a new person.

    private void tbCreatedBy_Validating(object sender, CancelEventArgs e)
    {
        int id;
        TextBox tb = (TextBox)sender;
        if (!string.IsNullOrEmpty(tb.Text) && !int.TryParse(tb.Text, out id))
        {
            e.Cancel = true;
        }
    }
    
    private void tbCreatedBy_Validated(object sender, EventArgs e)
    {
        int id;
        TextBox tb = (TextBox)sender;
        if (int.TryParse(tb.Text, out id))
        {
            Person person = context.People.Find(id);
            if (person != null)
            {        
                post.CreatedBy = person;
            }
            else
            {
                Person p = new Person();
                post.CreatedBy = p;
            }
            bsCreatedBy = new BindingSource(bs, "CreatedBy");
            tbCreatedByName.DataBindings.Clear();
            tbCreatedByName.DataBindings.Add("Text", bsCreatedBy, "Name");
        }
    }
    
    Best regards,


    Chester Hong
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help

    Monday, July 8, 2013 8:36 AM
    Moderator
  • I think I'm finally on the right road. I edited as you suggested in this way:

    private void button1_Click(object sender, EventArgs e)
            {
                using (var ctx = new MyContext())
                {
                    post = ctx.Posts.Find(1);
                    bs.DataSource = post;
    
                    tbId.DataBindings.Add("Text", bs, "Id");
                    tbTitle.DataBindings.Add("Text", bs, "Title");
                    bsCreatedBy = new BindingSource(bs, "CreatedBy");
                    tbCreatedByName.DataBindings.Add("Text", bsCreatedBy, "Name");
                    bsUpdatedBy = new BindingSource(bs, "UpdatedBy");
                    tbUpdatedByName.DataBindings.Add("Text", bsUpdatedBy, "Name");
                }
            }
    
            private void tbCreatedBy_Validating(object sender, CancelEventArgs e)
            {
                int id;
                TextBox tb = (TextBox)sender;
                if (!string.IsNullOrEmpty(tb.Text) && !int.TryParse(tb.Text, out id))
                {
                    e.Cancel = true;
                }
            }
    
            private void tbuUpdatedBy_Validating(object sender, CancelEventArgs e)
            {
                int id;
                TextBox tb = (TextBox)sender;
                if (!string.IsNullOrEmpty(tb.Text) && !int.TryParse(tb.Text, out id))
                {
                    e.Cancel = true;
                }
            }
    
            private void tbCreatedBy_Validated(object sender, EventArgs e)
            {
                using (var ctx = new MyContext())
                {
                    int id;
                    TextBox tb = (TextBox)sender;
                    if (int.TryParse(tb.Text, out id))
                    {
                        Person person = ctx.People.Find(id);
                        if (person != null)
                        {
                            post.CreatedBy = person;
                        }
                        else
                        {
                            Person p = new Person();
                            post.CreatedBy = p;
                        }
                        bsCreatedBy = new BindingSource(bs, "CreatedBy");
                        tbCreatedByName.DataBindings.Clear();
                        tbCreatedByName.DataBindings.Add("Text", bsCreatedBy, "Name");
                    }
                }
            }
    
            private void tbuUpdatedBy_Validated(object sender, EventArgs e)
            {
                using (var ctx = new MyContext())
                {
                    int id;
                    TextBox tb = (TextBox)sender;
                    if (int.TryParse(tb.Text, out id))
                    {
                        Person person = ctx.People.Find(id);
                        if (person != null)
                        {
                            post.UpdatedBy = person;
                        }
                        else
                        {
                            Person p = new Person();
                            post.UpdatedBy = p;
                        }
                        bsUpdatedBy = new BindingSource(bs, "UpdatedBy");
                        tbUpdatedByName.DataBindings.Clear();
                        tbUpdatedByName.DataBindings.Add("Text", bsUpdatedBy, "Name");
                    }
                }
            }

    However I encountered a problem, and I'll try to explain it. When I run:

    post = ctx.Posts.Find(1);

    If a row in the table contains CreatedBy = 1 Post and also UpdatedBy = 1 or CreatedBy UpdatedBy is equal to the first time, but only the first time, that I edit a TextBox automatically continues to change the other.

    Just can't seem to understand this behavior.

    I think the problem is with Lazy Loading.




    • Edited by piedatt80 Thursday, July 11, 2013 7:51 AM Error
    Tuesday, July 9, 2013 1:53 PM
  • Hi piedatt80,

    Sorry for late response.

    It seems a little strange that the issue comes only the first time. Do you mean when CreatedBy and UpdatedBy are the same, and the issue appears when you edit the id text boxes tbUpdatedBy or tbCreatedBy?

    I don't find the code how you initialize the tbUpdatedBy and tbCreatedBy. I'm not sure whether it will cause any problem. But I think you can try to add initialization code for tbUpdatedBy and tbCreatedBy like:

    tbCreatedBy.Text = post.CreatedBy.Id.ToString();
    tbUpdatedBy.Text = post.UpdatedBy.Id.ToString();
    Best regards,


    Chester Hong
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help

    Thursday, July 11, 2013 7:58 AM
    Moderator
  • I think I foundthe solution. I made ​​some changes to yourcode, but I need to know what you think. I have used again the datasource so as to useonly one bindigsource, but eliminating through the properties of DataBindings to CreatedBy and UpdatedBy.

    public partial class Form1 : Form
        {
            Post post;
    
            public Form1()
            {
                InitializeComponent();
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                using (var ctx = new MyContext())
                {
                    ctx.Configuration.LazyLoadingEnabled = false;
    
                    post = (from d in ctx.Posts
                                .Include("CreatedBy")
                                .Include("UpdatedBy")
                            where d.Id == 1
                            select d).Single();
    
                    bs.DataSource = post;
    
                    tbCreatedBy_Name.Text = post.CreatedBy.Name.ToString();
                    tbUpdatedBy_Name.Text = post.UpdatedBy.Name.ToString();
                }
            }
    
            private void tbCreatedBy_Validating(object sender, CancelEventArgs e)
            {
                int id;
                TextBox tb = (TextBox)sender;
                if (!string.IsNullOrEmpty(tb.Text) && !int.TryParse(tb.Text, out id))
                {
                    e.Cancel = true;
                }
            }
    
            private void tbuUpdatedBy_Validating(object sender, CancelEventArgs e)
            {
                int id;
                TextBox tb = (TextBox)sender;
                if (!string.IsNullOrEmpty(tb.Text) && !int.TryParse(tb.Text, out id))
                {
                    e.Cancel = true;
                }
            }
    
            private void tbCreatedBy_Validated(object sender, EventArgs e)
            {
                using (var ctx = new MyContext())
                {
                    int id;
                    TextBox tb = (TextBox)sender;
                    if (int.TryParse(tb.Text, out id))
                    {
                        Person person = ctx.People.Find(id);
                        if (person != null)
                        {
                            post.CreatedBy = person;
                            tbCreatedBy_Name.Text = post.CreatedBy.Name.ToString();
                        }
                        else
                        {
                            Person p = new Person();
                            post.CreatedBy = p;
                            tbCreatedBy_Name.Clear();
                        }
                    }
                }
            }
    
            private void tbuUpdatedBy_Validated(object sender, EventArgs e)
            {
                using (var ctx = new MyContext())
                {
                    int id;
                    TextBox tb = (TextBox)sender;
                    if (int.TryParse(tb.Text, out id))
                    {
                        Person person = ctx.People.Find(id);
                        if (person != null)
                        {
                            post.UpdatedBy = person;
                            tbUpdatedBy_Name.Text = post.UpdatedBy.Name.ToString();
                        }
                        else
                        {
                            Person p = new Person();
                            post.UpdatedBy = p;
                            tbUpdatedBy_Name.Clear();
                        }
                    }
                }
            }
        }
    What do you think, or what would you improve?
    Actually I think my problem was due to a failure to initialize CreatedBy and UpdatedBy. But I think I've definitely resolved.
    However I am surprised you haven't found similar solutions around the net or in books.

    I guess now I will attempt to bring the code in WPF.

    Waiting for your latest positive feedback.
    P.S.: sorry for my bad English.
     

    • Edited by piedatt80 Monday, July 15, 2013 9:30 AM Error
    • Marked as answer by piedatt80 Friday, July 18, 2014 3:27 PM
    Thursday, July 11, 2013 3:43 PM
  • Hi piedatt80,

    Thanks for sharing your solution. I think it will be good to display the CreateBy's name and UpdatedBy name. I guess you may need to add some other code if you want to update to the database after you edit the names? Because CreateBy and UpdatedBy and not binding to the BindingSource any more.

    Best regards,


    Chester Hong
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help

    Monday, July 15, 2013 4:53 PM
    Moderator