0

I am trying to join few tables and return all data from all tables in the list.

Here is the query I tried

List<TblCrrequests> mycr = (from ep in _context.TblCrrequests
                            join e in _context.TblCrExternalBudget on ep.CrId equals e.CrId
                            join i in _context.TblCrInternalbudget on ep.CrId equals i.CrId
                            join t in _context.TblExternalBudgetTypes on e.TypeId equals t.TypeId
                            where ep.ProjectCode == pcode
                            select ep)
                            .OrderByDescending(d => d.RequestedOn)
                            .Take(8).ToList<TblCrrequests>();

But on executing in this list variable, it only contain data for the main table only. Showing the child table array as empty. How to modify the query to get all those details from the main and child tables as well into the list variable. I am very new to linq..

I tried select ep,e,i,t which didn't work. Also if I don't need all columns from one table, is that possible?

Here is my class defenitions

public partial class TblCrrequests
{
        public TblCrrequests()
        {
            TblCrExternalBudget = new HashSet<TblCrExternalBudget>();
        }

        public int CrId { get; set; }
        public string ProjectCode { get; set; }
       
        public virtual ICollection<TblCrExternalBudget> TblCrExternalBudget { get; set; }
}

public partial class TblCrExternalBudget
{
        public int? CrId { get; set; }
        public int? TypeId { get; set; }

        public virtual TblCrrequests Cr { get; set; }
        public virtual TblExternalBudgetTypes Type { get; set; }
}

public partial class TblExternalBudgetTypes
{
        public TblExternalBudgetTypes()
        {
            TblCrExternalBudget = new HashSet<TblCrExternalBudget>();
        }

        public int TypeId { get; set; }
        public string TypeName { get; set; }

        public virtual ICollection<TblCrExternalBudget> TblCrExternalBudget { get; set; }
}
Sandeep Thomas
  • 4,303
  • 14
  • 61
  • 132

2 Answers2

2

Use Include() method on a related property you would like to load too. Something like this:

// in this case its assuming that 
var books = (from b in db.books.Include(p => p.Authors)
                    where <your filter criteria>
                    select b).ToList();

The Include() method instructs it to load book's Authors with it as well.

Koref Koref
  • 300
  • 4
  • 13
  • 2
    The disadvantage of `Include` is that it fetches all properties, even the ones that you don't plan to use, like the foreign keys. If you fetch School [10] with its 2000 Students, then every Student will have a SchoolId equalt to 10. It is a waste of processing power to transfer this value 10 over 2000 times. – Harald Coppoolse Jul 27 '20 at 09:15
  • @Koref Koref He already can find your suggest with a little google search. And also ever your code lines are not an answer for this case. – gurkan Jul 27 '20 at 09:41
2

Alas, you forgot to tell us the classes that you have. You also didn't inform us about the relations between the tables. I'll have to guess from your usage. Next question you ask consider to provide this information.

It seems that you have a table with Requests, and a table InternalBudgets. There seems to be a one-to-many relation between Requests and InternalBudgets: every Request has zero or more InternalBudgets, every InternalBudget belongs to exactly one Request, namely the one that the foreign key refers to.

There seems to be a similar one-to-many between Requests and ExternalBudgets.

Finally there is also a one-to-many between ExternalBudgets and ExternalBudgetTypes. Personally I would expect a many-to-many relation: every ExternalBudgetType is used by zero or more ExternalBudgets. This doesn't change the answer drastically.

If you've followed the entity framework conventions, you'll have classes similar to the following:

class Request
{
    public int Id {get; set;}
    ...

    // Every Request has zero or more InternalBudgets (one-to-many)
    public virtual ICollection<InternalBudget> InternalBudgets {get; set;}

    // Every Request has zero or more ExternalBudgets (one-to-many)
    public virtual ICollection<ExternalBudged> ExternalBudgets {get; set;}
}

The Internal and External budgets:

class InternalBudget
{
    public int Id {get; set;}
    ...

    // Every InternalBudget belongs to exactly one Request, using foreign key
    public int RequestId {get; set;}
    public virtual Request Request {get; set;}
}

class ExternalBudget
{
    public int Id {get; set;}
    ...

    // Every ExternalBudget belongs to exactly one Request, using foreign key
    public int RequestId {get; set;}
    public virtual Request Request {get; set;}

    // Every ExternalBudget has zero or more ExternalBudgetTypess (one-to-many)
    public virtual ICollection<ExternalBudgetType> ExternalBudgetTypes {get; set;}

}

Finally the ExternalBudgetTypes:

class ExternalBudgetType
{
    public int Id {get; set;}
    ...

    // Every ExternalBudgetType belongs to exactly one ExternalBudget, using foreign key
    public int ExternalBudgetId{get; set;}
    public virtual ExternalBudget ExternalBudget {get; set;}
}

Because you stuck to the conventions, this is all that entity framework needs to detect your tables, the relations between the tables and the primary and foreign keys

In entity framework the columns of the tables are represented by the non-virtual properties. The virtual properties represent the relations between the tables (one-to-many, many-to-many)

The foreign key is a column in the table. Hence it is non-virtual. The object that the foreign key refers to is not part of the table, hence if is virtual.

The "Many" side in xxx-to-many relations should be implemented with ICollection<...>, not with IList<...>. Ilist provide functionality that is undefined in databases:

Requests fetchedRequest = ...
InternalBudget internalBudget = fetchedRequest[3];

Can you say which internalBudget has list index [3]? Better not provide functionality that is not defined. ICollection<...> give you the possibility to Add / Delete / Count, all functionalities that have a defined meaning in a database.

One final tip: in your identifiers use plural nouns to refer to sequences; use singular nouns to refer to elements in these sequences. That makes LINQ statements easier to understand.

Back to your question

Requirement Given a projectCode, give me the requests that have this projectCode, with several properties of their Internal and External Budgets

Easy method: use the virtual ICollections

var projectCode = ...
var result = dbContext.Requests

    // Keep only the Requests that have projectCode
    .Where (request => request.ProjectCode == projectCode)

    // order the remaining Requests by descending RequestedOn date
    .OrderByDescending(request => request.RequestedOn)

    // Select the properties that you want:
    .Select(request => new
    {
        // Select only the Request properties that you plan to use
        Id = request.Id,
        Name = request.Name,
        ...

        // Internal budgets of this Request
        InternalBudgets = request.InternalBudgets.Select(budget => new
        {
             // Again, only the properties that you plan to use
             Id = budget.Id,
             ...

             // not Needed, you know the value
             // RequestId = budget.RequestId,
        })
        .ToList(),

        // External budgets of this Request
        ExternalBudgets = request.ExternalBudgets.Select(budget => new
        {
             ...

             ExternalBudgetTypes = budget.ExternalBudgetTypes
                 .Select(budgetType => ...)
                 .ToList(),
        })
        .ToList(),
    });

In words: from all Requests, keep only those Request that have a value of property ProjectCode equal to projectCode. Order the remaining Requests by descending value of property RequestedOn. Finally Select several properties of the Request, its internal budgets and its external budgets.

Entity framework knows the relations between the tables, and knows how to translate the use of your virtual properties in the correct (Group-)join.

Note: the result differs slightly from your solution. Your solution would give:

Request InternalBudget (for simplicity: leave out Externalbudget / type
  01         10
  02         12
  04         11
  01         14
  01         15
  02         13

My Solution gives:

  • Request 01 with its InternalBudgets {10, 14, 15}
  • Request 02 with its InternalBudgets {12, 13}
  • Request 05 without any InternalBudgets
  • Request 04 with its InternalBudget {11}

IMHO this is much more intuitive and more efficient: the properties of each Request items are transferred only once.

Note that you will also get the Requests that have no Internal/External budgets or types. If you don't want them, use a Where(... => ...Count != 0) to filter them out.

If you don't want "Items with their Sub-items" (which is in fact a GroupJoin), but a standard join, use SelectMany to flatten the result

Do the Join yourself

Some people don't like to use the virtual ICollection<...>. They prefer to do the (Group-)Joins themselves. In method syntax Joins with more than two tables look horrible, luckily you don't have to do a Join.

var result = dbContext.Requests
    .Where (request => request.ProjectCode == projectCode)
    .OrderByDescending(request => request.RequestedOn)
    .Select(request => new
    {
        // Request properties:
        Id = request.Id,
        Name = request.Name,
        ...

        // Internal budgets:
        InternalBudgets = dbContext.InternalBudgets
            .Where(budget => budget.RequestId == request.Id)
            .Select(budget => new
            {
                 Id = budget.Id,
                 ...
            })
            .ToList(),

        // External budgets:
        ExternalBudgets = dbContext.ExternalBudgets
            .Where(budget => budget.RequestId == request.Id)
            .Select(budget => new
            {
                 Id = budget.Id,
                 ...

                 BudgetTypes = dbContext.BudgetTypes
                     .Where(budgetType => budgetType.ExternalBudgetId == budget.Id)
                     .Select(budgetType => ...)
                     .ToList(),
            })
            .ToList(),
      });

I'm not sure if you can convince your project leader that this method is better readable, more reusable, easier to test and change than the other one with the virtual ICollections.

Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116
  • That was elegant.. Thanks a lot which give me lots of information even few I didn't understand which is my lack of knowledge.. – Sandeep Thomas Jul 27 '20 at 09:04