How to combine three tables as a data source for Binding in a DataGrid?

Dmitriy 96 Reputation points
2021-04-08T02:55:54.677+00:00

I am creating an WPF app with MVVM. I have three tables as sources of my data which I need to display in the DataGrid through the ItemsSource="{Binding}". How and where do I combine this three tables so I could use the combined data set for my binding?

To add more details.

In the DataGrid I need to display the list of all books with column1 as book title (table Titles) and column2 as all authors of the book (table Authors) so I am thinking I need to perform a natural join operation of the table Titles on table AuthorISBN on table Authors. This part I get, but in where and how, I don't.

Any insight would be greatly appreciated.

85519-struct2.png 85518-struct1.png

C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,232 questions
XAML
XAML
A language based on Extensible Markup Language (XML) that enables developers to specify a hierarchy of objects with a set of properties and logic.
762 questions
0 comments No comments
{count} votes

Accepted answer
  1. Peter Fleischer (former MVP) 19,231 Reputation points
    2021-04-08T12:03:26.577+00:00

    Hi,
    try following demo:

    XAML:

    <Window x:Class="WpfApp1.Window038"  
            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:WpfApp038"  
            mc:Ignorable="d"  
            Title="Dmitriy-2994 Datagrid embedded tables" Height="450" Width="800">  
      <Window.DataContext>  
        <local:ViewModel/>  
      </Window.DataContext>  
      <Grid>  
        <DataGrid ItemsSource="{Binding View}"/>  
      </Grid>  
    </Window>  
    

    And classes:

    using System.Collections.ObjectModel;  
    using System.ComponentModel;  
    using System.Linq;  
    using System.Windows;  
    using System.Windows.Data;  
      
    namespace WpfApp038  
    {  
      public class ViewModel  
      {  
        public ViewModel() =>cvs.Source = GetData();     
      
        ObservableCollection<Data> colData = new ObservableCollection<Data>();  
        CollectionViewSource cvs = new CollectionViewSource();  
        public ICollectionView View { get => cvs.View; }  
        private ObservableCollection<Data> GetData()  
        {  
          var colISBN = LoadAuthorISBN();  
          var colAuthors = LoadAuthors();  
          var colTitles = LoadTitles();  
          var query = from isbn in colISBN  
                      join author in colAuthors on isbn.AuthorID equals author.AuthorID  
                      join title in colTitles on isbn.ISBN equals title.AuthorISBN  
                      select new Data() { Author = $"{author.FirstName} {author.LastName}", ISBN = isbn.ISBN, Title = title.Title };  
          return new ObservableCollection<Data>(query);  
        }  
      
        private ObservableCollection<AuthorISBN> LoadAuthorISBN()  
        {  
          ObservableCollection<AuthorISBN > col = new ObservableCollection<AuthorISBN>();  
          for (int i = 1; i < 10; i++) col.Add(new AuthorISBN() { AuthorID = i, ISBN = $"ISBN {i}" });  
          return col;  
        }  
      
        private ObservableCollection<Authors> LoadAuthors()  
        {  
          ObservableCollection<Authors> col = new ObservableCollection<Authors>();  
          for (int i = 1; i < 10; i++) col.Add(new Authors() { AuthorID = i,FirstName = $"FirstName{i}", LastName = $"LastName{i}" });  
          return col;  
        }  
      
        private ObservableCollection<Titles>  LoadTitles()  
        {  
          ObservableCollection<Titles> col = new ObservableCollection<Titles>();  
          for (int i = 1; i < 10; i++) col.Add(new Titles() { AuthorISBN = $"ISBN {i}", Title=$"Title {i}" });  
          return col;  
        }  
      }  
      
      public class Data  
      {  
        public string Title { get; set; }  
        public string ISBN { get; set; }  
        public string Author { get; set; }  
      
      }  
      
      internal class AuthorISBN  
      {  
        internal int AuthorID { get; set; }  
        internal string ISBN { get; set; }  
      }  
      internal class Authors  
      {  
        internal int AuthorID { get; set; }  
        internal string FirstName { get; set; }  
        internal string LastName { get; set; }  
      }  
      internal class Titles  
      {  
        internal string AuthorISBN { get; set; }  
        internal string Title { get; set; }  
      }  
    }  
    

    Result:

    85775-x.png

    2 people found this answer helpful.

2 additional answers

Sort by: Most helpful
  1. DaisyTian-1203 11,616 Reputation points
    2021-04-08T07:15:48.8+00:00

    I make a demo based on your description as below:

    public class ViewModel  
        {  
            public ObservableCollection<Model> models { get; set; } = new ObservableCollection<Model>();  
            public ViewModel()  
            {  
                string connsql = @"Data Source = (localdb)\ProjectsV13; Initial Catalog = *******.......";  
                try  
                {  
                    using (SqlConnection conn = new SqlConnection())  
                    {  
                        conn.ConnectionString = connsql;  
                        conn.Open();  
                        String sql = "select T.Title,T.ISBN,A.FirstName,A.LastName from dbo.Titles as T inner join dbo.AuthorISBN as AI on T.ISBN = AI.ISBN inner join dbo.Author as A on AI.AuthorID = A.AuthorID";  
                        SqlCommand cmd = new SqlCommand(sql, conn);  
                        SqlDataReader dr = cmd.ExecuteReader();  
                        while (dr.Read())  
                        {  
                            Model model = new Model();  
                            model.FirstName = dr["FirstName"].ToString();  
                            model.LastName = dr["LastName"].ToString();  
                            model.Title = dr["Title"].ToString();  
                            models.Add(model);  
                        }  
                        conn.Close();  
                    }  
                }  
                catch (Exception ex)  
                {  
                    Debug.WriteLine(ex.Message.ToString());  
                }  
            }  
      
        }  
        public class Model  
        {  
            public string Title { get; set; }  
            public string FirstName { get; set; }  
            public string LastName { get; set; }  
        }  
    

    The Xaml is:

          <Window.DataContext>  
                <local:ViewModel></local:ViewModel>  
            </Window.DataContext>  
            <Grid>  
                <DataGrid ItemsSource="{Binding models}"></DataGrid>  
            </Grid>  
    

    The result picture is:
    85672-capture.png

    Did I miss any info for your requirement? If I did, please point out.


    If the response is helpful, please click "Accept Answer" and upvote it.
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    1 person found this answer helpful.

  2. Duane Arnold 3,211 Reputation points
    2021-04-08T08:19:50.077+00:00

    @Dmitriy

    IMHO, what you need to send is a custom class/object with properties in a List<T> the List<T> is blindable.

    You can do a join sure, but you should learn how to use a datareader.

    The link below should help you understand how to do the join and then select the column names via the datareader. It doesn't matter that it talking MS SQL server when you are using Access, becuase the use of T-SQL and using a ADO.NET datareader are the same even if using Access.

    http://www.mikepope.com/blog/AddComment.aspx?blogid=1718

    It's VB but you should be able to understand it and convert it to c#.

    https://www.tek-tips.com/faqs.cfm?fid=3727

    In this example, you can see how you can access the query results using column names via the datareader.

    https://www.c-sharpcorner.com/uploadfile/mahesh/datareader-in-ado-net/

    You can create a custom class/object a container object.

    https://gunnarpeipman.com/csharp-automatic-properties/

    https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/auto-implemented-properties

    https://www.c-sharpcorner.com/UploadFile/mahesh/create-a-list-of-objects-in-C-Sharp/

    instance a new list that's strong typed to the customx.cs

    customxlist List<customx> = new List<customx>();

    while in reader loop..

    var custx = new customx()

    populate the custx public properties from the datareader columns, which would be the selected columns you picked in the join of tables.

    add the custx to the customxlis

    end of loop..

    bind the customxlist to the control.

    HTH

    1 person found this answer helpful.