C# DataGrid绑定和将新行保存到数据库中的问题

C# DataGrid绑定和将新行保存到数据库中的问题,c#,winforms,entity-framework,datagridview,ado.net,C#,Winforms,Entity Framework,Datagridview,Ado.net,我正在使用SQL Server Compact和Entity Framework 6开发一个简单的windows应用程序,我有一个由数据库填充的TreeView,还有一个DataGrid,它应该根据TreeView中选择的节点填充。看起来像这样 数据库模型: 类别 类别 类别名称 子类别 苏比德 子名称 类别 项目 项目ID 项目名称 苏比德 我用这段代码填充了DataGrid,一切都很好,但它不会将新行保存到数据库中,尽管编辑现有行是可行的 private void tree

我正在使用SQL Server Compact和Entity Framework 6开发一个简单的windows应用程序,我有一个由数据库填充的TreeView,还有一个DataGrid,它应该根据TreeView中选择的节点填充。看起来像这样

数据库模型:

  • 类别
    • 类别
    • 类别名称
  • 子类别
    • 苏比德
    • 子名称
    • 类别
  • 项目
    • 项目ID
    • 项目名称
    • 苏比德

我用这段代码填充了DataGrid,一切都很好,但它不会将新行保存到数据库中,尽管编辑现有行是可行的

private void treeView_AfterSelect(object sender, TreeViewEventArgs e)
{
    if (treeView.SelectedNode.Level == 1)
    {
        BindingSource bs = new BindingSource();
        bs.DataSource = (from a in context.Items where a.SubID == (int)treeView.SelectedNode.Tag select a).ToList();
        dataGrid.DataSource = bs;
    }
}  

所以,我试过这个。保存新行和编辑已存在的行是有效的,问题是当在TreeView中选择一个节点时,它应该只显示链接到所选节点的,但它不会。它将新的查询结果添加到旧的查询结果,在DataGrid上显示这两个查询,如果我选择更多的节点,可能会更多

private void treeView_AfterSelect(object sender, TreeViewEventArgs e)
{
    if (treeView.SelectedNode.Level == 1)
    {
        context.Items.Where(a => a.SubID == (int)treeView.SelectedNode.Tag).Load();
        dataGrid.DataSource = context.Items.Local.ToBindingList();
    }
} 

我还想做一件事,当从TreeView中选择一个子类别时,我应该得到链接到DataGrid上显示的这个特定子类别的项目,因此,在DataGrid中添加新行时,它是否可以自动将项目中的子ID字段设置为选定节点的ID,将此新添加的项目链接到树视图中选定的子类别


如果有任何帮助,我将不胜感激,如果解决方案显而易见,我也会感到抱歉,但所有这些对我来说都是新鲜事。谢谢。

我对您使用的框架不是很熟悉,但我想尝试一下:

您的第一个代码块具有它的行为,因为
ToList
对符合条件的当前行集进行快照。当您添加新行时,您正在向该快照添加一个项目,但不涉及从中检索其他行的数据库

第二个代码块具有它的行为,因为上下文跟踪本地对象的缓存。调用的第一行只是将这些项带到缓存中,但是以前在缓存中的对象作为本地对象保留在那里

据我所知,这将是最好的解决方案:

  • 首次创建表单时,请创建一个。将此
    BindingSource
    设置为的。将
    BindingSource
    设置为
    context.Items.Local。(
    )。现在,
    BindingSource
    充当缓存项的代理

  • 当选择一个项目时,您可以像现在这样选择适当的实体,但也可以适当地设置
    BindingSource
    的属性。(文档建议
    Filter
    的语法应该与它看起来像SQL的语法相同,因此我打赌有某种方法可以将Linq查询转换为适当的字符串。)


  • 总体效果是您将使用来自底层数据库的数据。选择类别后,它将加载相应的实体。然后,它将筛选出它所知道的所有实体的列表,这些实体只是感兴趣的实体。

    对于选择另一个节点时出现的先前加载的项目,您应该在
    treeView\u AfterSelect
    中添加这一行:

    foreach(var item in context.Items.Local.ToList())
        context.Entry(item).State = System.Data.Entity.EntityState.Detached;
    
    然后继续

    context.Items.Where(a => a.SubID == (int)treeView.SelectedNode.Tag).Load();
    
    添加的行将从更改跟踪器中删除项目,这些项目将有效地从
    Local
    集合中删除。
    DbSet
    Local
    集合包含上下文已加载且未删除的所有项目。因此,在每个
    Load
    语句中,您都会不断向该集合添加项。通过将它们的状态更改为
    分离
    ,上下文不再“知道”它们的存在,它们从
    本地
    集合中消失

    请注意,您不能这样做

    context.Items.Local.Clear();
    
    因为除了从
    Local
    集合中删除它们之外,这将标记所有要删除的项<代码>保存更改将从数据库中删除项目


    至于向新的子类别添加新项目,最好将这些项目添加到
    子类别
    中的
    项目
    集合中。这样做,您不需要外键值(此时不知道),但在调用
    SaveChanges
    时,EF将首先保存子类别,并在项目中设置生成的FK值“即时”

    看起来像

    var subcat = new SubCategory();
    var item = new Item();
    subcat.Items.Add(item); // subcat.Items must have been initialized! 
    context.SubCategories.Add(subcat); // Also marks item as Added
    context.SaveChanges();
    
    现在EF首先保存
    子类别
    ,读取其生成的键值,设置
    的外键值并保存
    ,所有这些都在一个事务中完成


    如果只是为现有的
    子类别创建一个新项目
    ,则可以直接设置
    item.SubId
    。但是,如果将
    subcat
    附加到上下文,则仍然可以使用
    subcat.Items.Add(item)

    没错,是否可以在每次选择之前卸载对象?我读了一些书,也许这是唯一的方法,但这对我来说不是很好。我尝试了你的建议,不幸的是它对我不起作用,也许BindingSourceFilter不是这样做的方法。Thanks@Mody当前位置我真的不知道我在做什么,所以我对你的问题悬赏了。希望我们能从知道他们在做什么的人那里得到答案。@icktoofay多么慷慨啊!谢谢。非常感谢,但是当我从TreeView
    集合中选择另一个子类别时,我在Foreach行上遇到了这个错误;枚举操作可能无法执行。
    我尝试了
    context.Items.Local.Clear()ToList()
    。你是个救命恩人,谢谢。它工作得很好。如果你能解释一下代码,那就太好了