0

I have two models in my data access layer: Table1 and Table2.

I want to use the WinUI 3 DataGrid from the CommunityToolkit to display two columns from each table: Table1.ColumnA, Table1.ColumnB, Table2.ColumnC, Table2.ColumnD

My thought was to use linq in my ViewModel class to join the enumerable from each model:

IEnumerable<Table1> table1 = unitOfWorkDbGlobal.Table1.GetAll().ToList();
IEnumerable<Table2> table2 = unitOfWorkDbGlobal.Table2.GetAll().ToList();

                var JoinedTables = (from t1 in table1
                                join t2 in table2 on t1.TestGuid equals t2.TestGuid
                                select new
                                { t1.ColumnA, t1.ColumnB, 
                                t2.ColumnC, t2.ColumnD });

The problem that occurred with this approach is that I could create a CommunityToolkit.Mvvm [ObservableProperty] with table1 or table2 as needed, but I cannot create an observable property with the join because I'm using a var type. When I use JoinedTables.GetType().Name to determine the explicit type, it returns an Enumerable<JoinIterator>d__122 4 type, which appears to be computer gobbledygook unusable as a property type.

[ObservableProperty]
private ObservableCollection<Table1>? _table1Collection; //this works

[ObservableProperty]
private Enumerable<JoinIterator> d__122`4 _joinedTables; //Errors

How can the joined table be made into an ObservableProperty that can be bound in XAML to a CommunityToolkit DataGrid.

Here is an example of the XAML that I would like to use (note ViewModel is assigned in the code-behind as the class with the code that I added above):

        <controls:DataGrid x:Name="MyDataGrid"
        AutoGenerateColumns="False"
        ItemsSource="{x:Bind ViewModel.JoinedTables, Mode=OneWay}">
            <controls:DataGrid.Columns>
                <controls:DataGridTextColumn 
                Header="Column A" 
                Width="250"
                Binding="{Binding ColumnA}" 
                FontSize="14" />
                <controls:DataGridTextColumn 
                Header="Column B" 
                Width="250"
                Binding="{Binding ColumnB}" 
                FontSize="14" />
                <controls:DataGridTextColumn 
                Header="Column C" 
                Width="250"
                Binding="{Binding ColumnC}" 
                FontSize="14" />
                <controls:DataGridTextColumn 
                Header="Column D" 
                Width="250"
                Binding="{Binding ColumnD}" 
                FontSize="14" />
            </controls:DataGrid.Columns>
        </controls:DataGrid>
E. A. Bagby
  • 824
  • 9
  • 24

1 Answers1

2

You need to create a type for the joined tables. Something like this.

ViewModel.cs

using CommunityToolkit.Mvvm.ComponentModel;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

namespace DataGridTests;

public class Table1
{
    public int Id { get; set; }
    public string ColumnA { get; set; } = string.Empty;
    public string ColumnB { get; set; } = string.Empty;
}

public class Table2
{
    public int Id { get; set; }
    public string ColumnC { get; set; } = string.Empty;
    public string ColumnD { get; set; } = string.Empty;
}

public partial class JoinedTable : ObservableObject
{
    [ObservableProperty]
    private string columnA = string.Empty;

    partial void OnColumnAChanged(string value)
    {
        // Update database.
    }

    public string ColumnB { get; set; } = string.Empty;

    public string ColumnC { get; set; } = string.Empty;
    public string ColumnD { get; set; } = string.Empty;
}

public partial class MainWindowViewModel : ObservableObject
{
    [ObservableProperty]
    private ObservableCollection<JoinedTable> joinedTables = new();

    public MainWindowViewModel()
    {
        IEnumerable<JoinedTable> joinedTablesSource = Table1.Join(
            Table2,
            table1 => table1.Id,
            table2 => table2.Id,
            (table1, table2) => new JoinedTable()
            {
                ColumnA = table1.ColumnA,
                ColumnB = table1.ColumnB,
                ColumnC = table2.ColumnC,
                ColumnD = table2.ColumnD,
            });

        JoinedTables = new ObservableCollection<JoinedTable>(joinedTablesSource);
    }

    private ObservableCollection<Table1> Table1 { get; set; } = new()
    {
        {new Table1() { Id = 1, ColumnA="Table1-A-1", ColumnB="Table1-B-1" } },
        {new Table1() { Id = 2, ColumnA="Table1-A-2", ColumnB="Table1-B-2" } },
        {new Table1() { Id = 3, ColumnA="Table1-A-3", ColumnB="Table1-B-3" } },
    };

    private ObservableCollection<Table2> Table2 { get; set; } = new()
    {
        {new Table2() { Id = 1, ColumnC="Table2-C-1", ColumnD="Table2-D-1" } },
        {new Table2() { Id = 20, ColumnC="Table2-C-2", ColumnD="Table2-D-2" } },
        {new Table2() { Id = 3, ColumnC="Table2-C-3", ColumnD="Table2-D-3" } },
    };
}

.xaml

<toolkit:DataGrid ItemsSource="{x:Bind ViewModel.JoinedTables, Mode=OneWay}">
    <toolkit:DataGrid.Columns>
        <toolkit:DataGridTextColumn
            Binding="{Binding ColumnA, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
            Header="A"
            IsReadOnly="False" />
        <toolkit:DataGridTextColumn
            Binding="{Binding ColumnB}"
            Header="B" />
        <toolkit:DataGridTextColumn
            Binding="{Binding ColumnC}"
            Header="C" />
        <toolkit:DataGridTextColumn
            Binding="{Binding ColumnD}"
            Header="D" />
    </toolkit:DataGrid.Columns>
</toolkit:DataGrid>
Andrew KeepCoding
  • 7,040
  • 2
  • 14
  • 21
  • Basically, manufacturing a manually made model class solely for the use of joining the other models, correct? Is that the typical approach or are there other ways of handling joined model classes? If the binding mode is two-way, will this allow the database to be updated? – E. A. Bagby Jan 28 '23 at 18:59
  • Also, I noticed you have it in the ViewModel.cs. Would the custom model class for the joined models be better in a separate folder in the business layer or perhaps even the data access layer. – E. A. Bagby Jan 28 '23 at 19:02
  • 1
    AFAIK, you need a type with properties to show them in the ``DataGrid``. That's why you need a class like **JoinedTable**. – Andrew KeepCoding Jan 29 '23 at 14:45
  • 1
    I updated my answer with a ``TwoWay`` binding example. See the ColumnA in the JoinedTable. – Andrew KeepCoding Jan 29 '23 at 14:50
  • 2
    And for the last question about layers, since this is an example, I was just placing everything close. Of course, you should consider separating them to at least another file. – Andrew KeepCoding Jan 29 '23 at 14:55
  • I'll dig into this tomorrow. – E. A. Bagby Jan 29 '23 at 18:29