Search code examples
c#linqlinq-to-entities

Join Tables with Different DataType in Linq


I have two tables that I need to do left outer join table1 to table2, but table1 coursetitle is VARCHAR(1000) and table2 coursetitle is text datatype. I used ToString() for both fields and I still get errors. Below is my join query:

IQueryable<joinedTable> qry=(from a in db.table1
                join b in db.table2 on
                new {SUBJECT=a.SUBJECT, CATALOG_NBR=a.CATALOG_NBR, COURSETITLE=a.COURSETITLE.ToString() } equals
                new { SUBJECT = b.SUBJECT, CATALOG_NBR = b.CATALOG_NBR, COURSETITLE=b.COURSETITLE.ToString() } into ab
                from x  in ab.DefaultIfEmpty()
                select new joinedTable()
                {
                ID=a.ID,
                SUBJECT=a.SUBJECT,
                CATALOG_NBR=a.CATALOG_NBR,
                COURSETITLE=a.COURSETITLE,
                DESCR=x.DESCR
                }.AsQueryable();

The Error:

"The data types varchar and text are incompatible in the equal to operator."


Solution

  • Use a pre-select to make sure that the keys on which you want to join are equal:

    var preselectTable1 = db.Table1.Select(row => new
    {
        JoinKey = new
        {
            Subject = row.SUBJECT,
            CatalogNbr = row.CATALOG_NBR,
            CourseTitle = row.COURSETITLE.ToString(),
        },
    
        ... // select the other table1 properties that you want to put in your final result
    });
    
    var preselectTable2 = db.Table2.Select(row => new
    {
        JoinKey = new
        {
            Subject = row.SUBJECT,
            CatalogNbr = row.CATALOG_NBR,
            CourseTitle = row.COURSETITLE.ToString(),
        },
    
        ... // select the other table2 properties that you want to put in your final result
    });
    

    Now both JoinKeys are exactly the same type. Note: he queries are not executed yet, the database is not contacted, only the Expression of the query is adjusted.

    Whenever you need "Customers with their Orders", "Schools with their Students", "Rows from table1 with their rows from table2", consider to use one of the overloads of GroupJoin, instead of Join. I usually use the overload with a parameter resultSelector, so I can fine tune my results.

    To get the table1 rows, each with its zero or more matching table2 rows, use GroupJoin:

    var result = preselectTable1.GroupJoin(preselectTable2,
    
        table1Row => table1Row.JoinKey,    // from every row in table1 get the joinKey
        table2Row => table2Row.JoinKey,    // from every row in table2 get the joinKey
    
        // parameter result selector: from every row in table 1 and all rows in table2
        // that have the same value for JoinKey make one new:
        (table1Row, matchingTable2Rows) => new
        {
             // select the table1 properties that you want to use
             Id = table1Row.Id,
             Name = table1Row.Name,
             ...
    
             matching2Items = matchingTable2Rows.Select(table2Row  => new
             {
                 // select the table2 properties that you want.
                 Id = table2Row.Id,
                 Name = table2Row.Name,
                 ...
             })
             .ToList(),
        });
    

    In the example above the result seems a bit strange. That is because I don't know what table1 and table2 are. If you would do the same to get "Customer and their Orders", the result would look very natural:

    IQueryable<Customer> customers = ...
    IQueryable<Order> orders = ...
    
    var customersWithTheirOrders = customers.GroupJoin(orders,
    
        customer => customer.Id,         // from each Customer take the primary key
        order => order.CustomerId,       // from each order take the foreign key
    
        (customer, ordersOfThisCustomer) => new
        {
            Id = customer.Id,
    
            AddressLabel = new AddressLabel
            {
                Name = customer.Name,
                Street = customer.Street,
                City = ...
            }
    
            Orders = ordersOfThisCustomer.Select(order => new
            {
                 Id = order.Id,
                 Date = order.Date,
                 Total = order.Total,
                 ...
            })
            .ToList(),
        });
    

    Apart from that it feels more natural to get "table1 items with their matching table2 items" than a sequence of combinations of "a table1 item with a matching table 2 item", one of the advantages of a groupjoin is that the item1 properties are transferred only once.

    If School[10] has 2000 Students, it is way more efficient to transfer the data of School[10] only once, than to transfer the same school data once for every of its 2000 students.

    If you really want a flattened end result, use SelectMany.