Entity Framework, где в предложении о связанных данных отсутствуют столбцы - PullRequest
0 голосов
/ 16 января 2020

У меня есть 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

Теперь я хотел бы отфильтровать в своей навигации свойство (таблица B). Я попробовал следующие 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], 

Я понимаю, что свойство навигации не предназначено для возможности фильтрации, но эти запросы верны, за исключением отсутствующих столбцов в Таблице A.

Я использовал таблицы для упрощения кода. Я подробно опишу, как я отображаю сетки основных данных, чтобы вы могли видеть используемый контекст данных. Я упростил реальный код, поэтому только соответствующие детали. Надеюсь, это прояснит, как работает текущий код. Я не уверен, как я должен изменить 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: свойство 'Description' не найдено в 'object' '' <> f__AnonymousType0 2' (HashCode=580396885)'. BindingExpression:Path=Description; DataItem='<>f__AnonymousType0 2 '(HashCode = 580396885); целевым элементом является ValueHolder (HashCode = 62039823); Целевым свойством является «Значение» (тип «Объект»)

1 Ответ

0 голосов
/ 17 января 2020

Правильные свойства навигации в объекте не могут быть отфильтрованы, хотя можно применить фильтр низкого уровня к 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 => a.TableB), чтобы загрузить их, однако, независимо от того, загружены они или нет, TableA.TableB будет не фильтроваться. Он будет представлять все «B», связанные с каждым «A». Отфильтрованные B доступны с помощью коллекции USDTableBs, возвращаемой вместе с каждым TableA в анонимном типе.

Так, например:

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

Редактировать: для привязки к сетке данных вам лучше определение ViewModel для TableA и связанных с ним записей TableB. Если вы изначально привязывали TableA, этот новый запрос возвращает анонимный тип, поэтому старые привязки для «ID» не будут правильными. Я не рекомендую привязывать напрямую к сущностям, потому что даже если вы отображаете на сущность и загружаете только отфильтрованные TableB, это может сбить с толку, если эта отфильтрованная сущность позже попытается присоединиться к DbContext и сохранится, так как она не представляет завершенную изображение сущности.

Для привязки к анонимному типу вы должны заменить любые привязки, такие как «Id», на «TableA.Id» и т. д. c. И любые привязки для "TableB.x" к "USDTableBs.x". В качестве альтернативы с ViewModel для TableA и связанных с ним 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>

Привязки для первого источника представления должны указывать на поля в объекте «Мастера». Привязки для второго ожидают коллекцию "Детали" для деталей. Чтобы структурировать структуру анонимного типа, она должна выглядеть примерно так:

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()
                    });

Разница в том, что выбранные объекты - это детали из мастера, которые соответствуют каждой главной строке, как если бы она была связана с главной строкой. Ваши привязки ожидают поля в возвращаемых элементах коллекции, а не объект, содержащий эти поля. Раздел подробностей ожидает Master.Details, поэтому в нашем случае нам все еще нужно называть его «Подробности», но вместо привязки к a.Details он привязывается к нашему выбранному подмножеству.

Альтернатива с Ваше старое выражение Linq - это не индивидуальная привязка к «Id», вам нужно было бы привязать к «Details.Id», потому что вы выбрали основную запись как «Детали» и вместо привязки источника представления 2-й коллекции к «Подробности» вам нужно было бы привязать его к доллару США в зависимости от того, как вы выбрали эту коллекцию в Linq.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...