简体   繁体   中英

Translating SQL Query to Linq for use with Entity Framework

looking for some help with this - I am new to Entity Framework / Linq method of queries. The below SQL statement fetches the data I need from a SQL Server Database.

Can anyone help with what this should look like in Linq so I can get an idea of how this works?

SELECT st.departure_time, t.trip_headsign, r.route_short_name, r.route_long_name
FROM stop_times st
LEFT JOIN stops s ON s.stop_id = st.stop_id
LEFT JOIN trips t ON t.trip_id = st.trip_id
LEFT JOIN routes r ON r.route_id = t.route_id
LEFT JOIN calendar c ON c.service_id = t.service_id
WHERE st.stop_id = '2560378' AND c.start_date <= 20190122 AND c.end_date >= 20190122 AND c.tuesday = 1
ORDER BY st.departure_time ASC

Using Entity like the below etc would select all the stops

using (var db = new TestEntities())
{
    var query = from b in db.Stops select b;
}

I will start you off with your first couple of joins, so you can get the rest. You will also need to finish the WHERE clause.

            var query = (from ST in db.stop_times
                         join S in db.stops on ST.stop_id equals S.stop_id into Ss
                         from S in Ss.DefaultIfEmpty()
                         join T in db.trips on ST.trip_id equals T.trip_id into Ts
                         from T in Ts.DefaultIfEmpty()
                         where ST.stop_id = '2560378'
                         select new YourCustomObject
                         {
                             DepartureTime = ST.departure_time,
                             TripHeadsign = T.trip_headsign
                         }).OrderBy(x => x.DepartureTime);

As you can see the following is a LEFT JOIN :

join T in db.trips on ST.trip_id equals T.trip_id into Ts
                             from T in Ts.DefaultIfEmpty()

Alas you forgot to provide the relevant parts of your classes. Now we have to extract them from your SQL, hoping we draw the correct conclusions. Next time consider adding your class definitions.

Furthermore it might be handy to know the requirement on which you based your SQL query, now we'll have to extract the requirement from your SQL.

Requirement: The StopTime with Id == 2560378 belongs to one Trip. This Trip has zero or more Routes. From this StopTime, take the Trip and all its Routes that have StartDate <= ... and EndDate >= ... and TuesDay == 1. From the resulting items take the properties DepartureTime, StopTime, HeadSign, ...

Classes

It seems to me that your database has tables of StopTimes, Stops, Trips and Calendars.

Apparently there is a relation between them. Some might by one-to-many, some may be many-to-many, or one-to-zero-or-one. From your query it is difficult to determine these relations.

It seems to me that every Trip has zero or more StopTimes , and every StopTime belongs to exactly one Trip (one-to-many). There is also a one-to-many between StopTimes and Stops : every StopTime has zero or more Stops , and every Stop belongs to exactly one StopTime . Furthermore: a Trip has several Routes and also several Calendars .

It might be that some of these relations are not one-to-many, but many-to-many, or one-to-one. The principle remains the same.

If you've followed the e ntity framework code first conventions, your classes will be similar to the following:

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

    // Every Trip has zero or more StopTimes (one-to-many):
    public virtual ICollection<StopTime> StopTimes {get; set;}
    // Every Trip has zero or more Routes (one-to-many):
    public virtual ICollection<Route> Routes {get; set;}
    // Every Trip has zero or more Calendars (one-to-many):
    public virtual ICollection<Calendar> Calendars {get; set;}
}

class StopTime
{
    public int Id {get; set;}
    ...
    // Every StopTime belongs to exactly one Trip using foreign key:
    public int TripId {get; set;}
    public virtual Trip Trip {get; set;}

    // Every StopTime has zero or more Stops (one-to-many):
    public virtual ICollection<Stop> Stops {get; set;}
}

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

    // every Route belongs to exactly one Trip (using foreign key)
    public int TripId {get; set;}
    public virtual Trip Trip {get; set;}
}

Etc: Stops and Calendars will be very similar.

In entity framework, the columns of your tables are represented by non-virtual properties. The virtual properties represent the relations between the tables.

Because I followed the conventions, entity framework is able to detect the primary and foreign keys and the relations between the tables. There is no need for attributes, nor fluent API. If you want to use different identifiers, attributes or fluent API might be needed.

Query using virtual properties

Once you've designed your classes correctly, especially the relations between the tables, (virtual ICollection) your query will be simple:

var result = dbContext.StopTimes
    .Where(stopTime => stopTime.Id == 2560378)
    .SelectMany(stopTime => stopTime.Trip.Routes
         .Where(route => route.StartDate <= 20190122 && route.EndDate >= 20190122)
    (stopTime, route) => new
    {
        DepartureTime = stopTime.DepartureTime,
        TripHeadSign = stopTime.Trip.HeadSign,
        Route = new
        {
            ShortName = route.ShortName,
            LongName = route.LongName,
        },

        // or, if you don't want a separate Route Property:
        RouteShortName = route.ShortName,
        RouteLongName = route.LongName,
    })
    .OrderBy(item => item.DepartureTime);

Because entity framework knows my relations it knows which (Group)joins to perform when you use the virtual properties.

Query performing an actual Join

Some people really prefer to use joins. Well if you can convince your project leader that the following is better readable / testable / maintainable:

var result = dbContext.StopTimes
    .Where(stopTime => stopTime.Id == 2560378) // keep only StopTimes with Id == ...
    .Join(dbContext.Trips,                     // Join with Trips
    stopTime => stopTime.TripId,               // from every StopTime take the TripId
    trip => trip.Id                            // from every Trip take the Id,
    (stopTime, trip) => new                    // keep only the properties I need
    {
        DepartureTime = stopTime.DepartureTime,
        TripHeadSign = trip.HeadSign
        TripId = trip.Id,                      // Id is used in the next join
    }
    // join this joinResult with the eligible routes:
    .Join(dbContext.Routes
          .Where(route => route.StartDate <= ... && route.EndDate >= ...)
    firstJoinResult => firstJoinResult.TripId,
    route => route.TripId,
    (firstJoinResult, route) =>  new
    {
        DepartureTime = firstJoinResult.DepartureTime,
        TripHeadSign = firstJoinResult.TripHeadSign,
        Route = new
        {
            ShortName = route.ShortName,
            LongName = route.LongName,
        },
    })
    .OrderBy(item => item.DepartureTime);
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM