【问题标题】:Entity Framework where clause on related data is missing columns实体框架 where 相关数据的子句缺少列
【发布时间】:2020-01-23 05:08:32
【问题描述】:

我有 2 个表 TableA 和 TableB 与 ID 列链接。这是一个 1:N 的关系。我有以下代码

var query = from b in _ctx.TableA
                .Where(b => b.Flag == true)
                .Include(c => c.TableB)
            select b;

这会产生以下选择语句,在主从网格中正确显示我的数据:

SELECT 
    [Project1].[C5] AS [C1], 
    [Project1].[Id] AS [Id], 
    [Project1].[ProductType] AS [ProductType], 
    [Project1].[Ccy] AS [Ccy], 
    [Project1].[Flag] AS [Flag], 
    [Project1].[C6] AS [C6], 
    [Project1].[Id1] AS [Id1]

    FROM ( SELECT 
        [Extent1].[Id] AS [Id], 
        [Extent1].[Description] AS [Description], 
        [Extent1].[ProductType] AS [ProductType], 
        [Extent1].[Flag] AS [Flag], 
        1 AS [C5], 
        [Extent2].[Id] AS [Id1], 
        [Extent2].[Ccy] AS [Ccy], 
        CASE WHEN ([Extent2].[Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C6]
        FROM  [dbo].[TableA] AS [Extent1]
        LEFT OUTER JOIN [dbo].[TableB] AS [Extent2] ON [Extent1].[Id] = [Extent2].[Id]
        WHERE (1 = [Extent1].[Flag]) AND ([Extent1].[Flag] IS NOT NULL)
    )  AS [Project1]
    ORDER BY [Project1].[Id] ASC, [Project1].[C6] ASC

现在,我想过滤掉我的导航属性 (TableB)。我尝试了以下 3 种方法,并且通过所有 3 种方法得到了以下 SQL,但它缺少 TableA 中的列,例如 ProductType 和 Ccy。

var query = from b in _ctx.TableA
.Where(b => b.Flag == true)
.Include(c => c.TableB).Select(o => o.TableB.Where(od => od.Ccy == "USD"))
select b;

var query = from b in _ctx.TableA
.Where(b => b.Flag == true)
.Include(c => c.TableB)
.SelectMany(o => o.TableB.Where(od => od.Ccy == "USD"))
select b;


var query = from p in _ctx.TableA
join ps in _ctx.TableB on p.Id equals ps.Id
where ps.Ccy == "USD"
select new { p, ps };
SELECT [Project1].[Id] AS [Id], 
    [Project1].[C1] AS [C1], 
    [Project1].[Id1] AS [Id1], 
    [Project1].[Ccy] AS [Ccy], 
    FROM ( SELECT 
        [Extent1].[Id] AS [Id], 
        [Extent2].[Id] AS [Id1], 
        [Extent2].[Ccy] AS [Ccy], 
        CASE WHEN ([Extent2].[Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
        FROM  [dbo].[TableA] AS [Extent1]
        LEFT OUTER JOIN [dbo].[TableB] AS [Extent2] 
        ON ([Extent1].[Id] = [Extent2].[Id]) 
        AND ('USD' = [Extent2].[Ccy]) AND ([Extent2].[Ccy] IS NOT NULL)
        WHERE (1 = [Extent1].[Flag]) AND ([Extent1].[Flag] IS NOT NULL)
    )  AS [Project1]
    ORDER BY [Project1].[Id] ASC, [Project1].[C1] ASC

如何修改上述 3 个查询之一以包含这些列:

[Project1].[ProductType] AS [ProductType], 
[Project1].[Ccy] AS [Ccy], 
[Project1].[Flag] AS [Flag], 

我了解导航属性并不意味着能够过滤,但这些查询是正确的,除了 TableA 上缺少的列。

我使用表格来简化代码。我将详细说明我是如何显示主从网格的,这样您就可以看到正在使用的数据上下文。我简化了实际代码,所以只有相关的细节。希望这将阐明当前代码的工作方式。我不确定我应该如何修改 xaml 以使绑定正常工作而不会出现这些错误:


   [Table("viewA")]
    public class Master
    {
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
        public Investment()
        {
            Prices = new HashSet<CcyHistory>();
        }

        [StringLength(150)]
        [Column(TypeName = "varchar")]
        public string Id { get; set; }

        [StringLength(150)]
        [Column(TypeName = "varchar")]
        public string Description { get; set; }

        public bool? Flag { get; set; }


        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]

        public ICollection<CcyHistory> Details { get; set; }
    }


    [Table("viewB")]
    public class CcyHistory
    {

        [Key]
        [StringLength(150)]
        [Column(Order = 0, TypeName = "varchar")]
        public string Id { get; set; }


        [StringLength(10)]
        [Column(TypeName = "varchar")]
        public string Ccy { get; set; }


        public virtual Master Master { get; set; }
    }


    public partial class MyContext : DbContext
    {
        public MyContext()
            : base("name=MyContext")
        {
            Database.SetInitializer<MyContext>(null);
        }

        public virtual DbSet<Master> Masters { get; set; }
        public virtual DbSet<CcyHistory> Details { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {

        }
    }




    public partial class MyView : Window
    {
        MyViewModel viewModel;

        public MyContext _ctx = new MyContext();

               .......


         public MyView()
        {
            viewModel = new MyViewModel();

            InitializeComponent();
            DataContext = viewModel;
        }

        private async void Window_Loaded(object sender, RoutedEventArgs e)
        {
            System.Windows.Data.CollectionViewSource myViewSource = ((System.Windows.Data.CollectionViewSource)(this.FindResource("myViewSource")));


            // this works fine
            var query = from b in _ctx.Masters
                       .Where(b => b.Flag == true)
                       .Include(c => c.Details)
                       select b;


            // this query cannot be bound to xaml
            //var query = from a in _ctx.Masters
            //            .Where(a => a.Flag == true)
            //            .Select(a => new
            //            {
            //                Details = a,
            //                USD = a.Details.Where(od => od.Ccy == "USD").ToList()
            //            })
            //            select a;


           myViewSource.Source = query.ToList();


XAML:

        <Window.Resources>
                <CollectionViewSource x:Key="myViewSource" d:DesignSource="d:DesignInstance {x:Type local:Masters},CreatList=True}"/>

            <CollectionViewSource x:Key="myDetailsViewSource" Source="{Binding Details, Source={StaticResource myViewSource}}">
            </CollectionViewSource>
            </Window.Resources>

        .....

            <Grid Name="MyGrid" DataContext="{StaticResource myViewSource}" >


                       <igDP:XamDataGrid x:Name="masterGrid" Grid.Row="1" DataSource="{Binding}" IsSynchronizedWithCurrentItem="True">

                            <igDP:XamDataGrid.FieldSettings>
                                <igDP:FieldSettings Width="Auto" AllowEdit="False"/>
                            </igDP:XamDataGrid.FieldSettings>

                            <igDP:XamDataGrid.FieldLayoutSettings>
                                <igDP:FieldLayoutSettings AutoGenerateFields="False" SelectionTypeRecord="Extended" SelectionTypeCell="None" />
                            </igDP:XamDataGrid.FieldLayoutSettings>

                            <igDP:XamDataGrid.FieldLayouts>
                                <igDP:FieldLayout>

                                    <igDP:UnboundField Name="ID" BindingPath="Id" BindingMode="TwoWay">
                                        <igDP:UnboundField.Settings>
                                            <igDP:FieldSettings CellClickAction="SelectRecord">
                                            </igDP:FieldSettings>
                                        </igDP:UnboundField.Settings>
                                    </igDP:UnboundField>


                                    <igDP:UnboundField Name="Description" BindingPath="Description" BindingMode="TwoWay">
                                        <igDP:UnboundField.Settings>
                                            <igDP:FieldSettings CellClickAction="SelectRecord">
                                            </igDP:FieldSettings>
                                        </igDP:UnboundField.Settings>
                                    </igDP:UnboundField>

                                    <igDP:UnboundField Name="Held" BindingPath ="Flag">
                                        <igDP:Field.Settings>
                                            <igDP:FieldSettings CellClickAction="SelectRecord"/>
                                        </igDP:Field.Settings>
                                    </igDP:UnboundField>


                                </igDP:FieldLayout>
                            </igDP:XamDataGrid.FieldLayouts>

                        </igDP:XamDataGrid>



                        <igDP:XamDataGrid x:Name="detailsDataGrid" Grid.Row="2" DataSource="{Binding Source={StaticResource myDetailsViewSource}}" >


                            <igDP:XamDataGrid.FieldSettings>
                                <igDP:FieldSettings Width="Auto" AllowEdit="False"/>
                            </igDP:XamDataGrid.FieldSettings>

                            <igDP:XamDataGrid.FieldLayoutSettings>
                                <igDP:FieldLayoutSettings AutoGenerateFields="False" SelectionTypeRecord="Extended" SelectionTypeCell="None" />
                            </igDP:XamDataGrid.FieldLayoutSettings>

                            <igDP:XamDataGrid.FieldLayouts>
                                <igDP:FieldLayout>

                                    <igDP:UnboundField Name="ID" BindingPath="Id"  Width="Auto" BindingMode="TwoWay">
                                        <igDP:UnboundField.Settings>
                                            <igDP:FieldSettings AllowEdit="False" CellClickAction="SelectRecord">
                                            </igDP:FieldSettings>
                                        </igDP:UnboundField.Settings>
                                    </igDP:UnboundField>


                                    <igDP:UnboundField Name="Ccy"  BindingPath="Ccy"  Width="Auto" >
                                        <igDP:Field.Settings>
                                            <igDP:FieldSettings AllowEdit="False" />
                                        </igDP:Field.Settings>
                                    </igDP:UnboundField>

                                </igDP:FieldLayout>
                            </igDP:XamDataGrid.FieldLayouts>

                        </igDP:XamDataGrid>

                    </Grid>



例如,我遇到的错误之一是:

System.Windows.Data 错误:40:BindingExpression 路径错误:在“对象”''f__AnonymousType02' (HashCode=580396885)'. BindingExpression:Path=Description; DataItem='&lt;&gt;f__AnonymousType02'(HashCode=580396885)上找不到“描述”属性;目标元素是“ValueHolder”(HashCode=62039823);目标属性是“值”(类型“对象”)

【问题讨论】:

    标签: entity-framework linq


    【解决方案1】:

    正确,无法过滤实体中的导航属性,尽管可以在 DbSet 上应用低级别过滤器,例如 IsActive 或可能的租户。 (多租户系统中的TenantId)

    如果您希望“A”记录只包含符合您条件的“B”记录,那么以下将执行此操作:

    var query = from a in _ctx.TableA
    .Where(a => a.Flag == true)
    .Select(a => new 
    { 
        TableA = a,
        USDTableBs = a.TableB.Where(od => od.Ccy == "USD").ToList()
    });
    

    需要注意的关键事项: 生成的对象将包括一个 TableA 引用和一组过滤后的 TableB。在上面的示例中,TableA.TableB 引用不是预先加载的。您可以添加 .Include(a =&gt; a.TableB) 来加载它们,但是无论是预先加载还是延迟加载,TableA.TableB 都不会被过滤。它将代表与每个“A”相关联的所有“B”。过滤后的 B 可以使用与匿名类型的每个 TableA 一起返回的 USDTableBs 集合来访问。

    例如:

    var tableA = query.First().TableA;
    var filteredBs = query.First().USDTableBs; // not tableA.TableBs
    

    编辑:要绑定到数据网格,您最好为 TableA 定义一个 ViewModel 及其关联的 TableB 记录。如果您最初绑定到 TableA,则此新查询将返回匿名类型,因此“ID”的旧绑定将不正确。我不建议直接绑定到实体,因为即使您映射到一个实体并仅加载过滤后的 TableB,如果该过滤后的实体稍后尝试附加到 DbContext 并保存,因为它不代表一个完整的实体的图片。

    要绑定到匿名类型,您将替换任何绑定,例如“Id”到“TableA.Id”等。以及“TableB.x”到“USDTableBs.x”的任何绑定。或者,使用 TableA 的 ViewModel 及其关联的 TableB,创建一个 POCO TableAViewModel 和 TableBViewModel,其中仅包含您的视图需要的字段并像这样加载它们:

    var query = from a in _ctx.TableA
    .Where(a => a.Flag == true)
    .Select(a => new TableAViewModel
    { 
        Id = a.Id,
        // ... any fields from TableA to display...
        TableB = a.TableB
            .Where(od => od.Ccy == "USD")
            .Select(od => new TableBViewModel
            {
                Id = od.Id,
                Ccy = od.Ccy,
                // ... any fields from TableB to display ...
            }).ToList()
    });
    

    这应该返回您的 WPF 视图将绑定到的结构,以代替您最初使用的实体。 ViewModel 方法的优点是您可以根据需要过滤它们,并将查询优化到所需的数据量,并且不必担心将它们与可能导致错误和错误的实体混淆。

    编辑:根据您为绑定提供的详细信息,您有 2 个集合视图源。第一个用于项目列表,第二个用于所选项目的详细信息:

    <CollectionViewSource x:Key="myViewSource" d:DesignSource="d:DesignInstance {x:Type local:Masters},CreatList=True}"/>
    
            <CollectionViewSource x:Key="myDetailsViewSource" Source="{Binding Details, Source={StaticResource myViewSource}}">
            </CollectionViewSource>
    

    第一个视图源的绑定期望指向 Masters 实体中的字段。第二个绑定需要一个“详细信息”集合来获取详细信息。要构建匿名类型结构,它应该如下所示:

    var query = _ctx.Masters
                        .Where(a => a.Flag == true)
                        .Select(a => new
                        {
                            a.ID,
                            a.Description,
                            a.Flag 
                            Details = a.Details.Where(od => od.Ccy == "USD").ToList()
                        });
    

    此处的区别在于,所选实体是来自主控的详细信息,以匹配每个主控行,就好像它已绑定到主控行一样。您的绑定需要返回的集合项上的字段,而不是包含这些字段的对象。 details 部分需要 Master.Details,因此在我们的例子中,我们仍然需要将其称为“Details”,但它不会绑定到 a.Details,而是绑定到我们选择的子集。

    您的旧 Linq 表达式的替代方法不是单独绑定到“Id”,而是需要绑定到“Details.Id”,因为您选择主记录作为“详细信息”而不是绑定第二个集合查看源代码到“详细信息”,您需要根据您在 Linq 中选择该集合的方式将其绑定到“美元”。

    【讨论】:

    • 我可以看到正在执行的正确查询。我遇到的问题是我收到错误,因此没有显示任何记录。如果我在 TableB 上没有 ccy 过滤器的情况下切换到我的原始查询,那么一切都很好。我正在使用 WPF 并在数据网格中显示。这是我得到的错误:
    • System.Windows.Data 错误:40:BindingExpression 路径错误:在 'object' ''f__AnonymousType02' (HashCode=-1011284684)'. BindingExpression:Path=Id; DataItem='&lt;&gt;f__AnonymousType02' 上找不到 'Id' 属性(HashCode=-1011284684);目标元素是“ValueHolder”(HashCode=51530997);目标属性是“值”(类型“对象”)
    • 另外,我所做的只是您建议的 var 查询。我不确定我是否理解您对 var TableA 和 var filteredBs 的建议。
    • 当我在 SSMS 中剪切并粘贴查询时,我得到完全相同的列和列名。唯一的例外是数据记录的数量,因为我在 Ccy 上进行了过滤。
    • 我已经扩展了答案以涵盖您的绑定问题。我相信您仍然可以绑定到匿名类型,但我建议使用视图模型来表示适合您视图的过滤模型。
    猜你喜欢
    • 2011-12-31
    • 2011-11-01
    • 2011-10-21
    • 2010-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多