1

I've looked at many SO posts but have not found 1 that answers my question. I'm new to LINQ so I think it's a very simple, I'm just not getting it.

I have 2 simple entities: Address and State, defined as follows:

public class Address
{
    [Key]
    public int Id { get; set; }
    public string CityName { get; set; }
    public int? StateId { get; set; }
    [ForeignKey("StateId")]
    public virtual State State { get; set; }
}


public class State
{
    [Key]
    public int Id { get; set; }
    public string StateName { get; set; }
}

All I'm trying to do is return an IQueryable of Addresses, but I want to do the equivalent of a SQL left outer join and return the StateName in the Address.

Here's what I have so far that returns Addresses that have a city that begins with "I". This works great. I just need to join to the state table and get the StateName.

[HttpGet]
public IQueryable<Address> Addresses()
{
  var query = from a in _contextProvider.Context.Addresses
  where a.CityName.StartsWith("I")
  select a;
  return query;
}
mwill
  • 424
  • 7
  • 21

1 Answers1

1

Your Address class already has a State property that represents the relation. Why don't you just use that?

public IQueryable<string> Addresses()
{
    var query = from a in _contextProvider.Context.Addresses
                where a.CityName.StartsWith("I")
                select a.State.StateName;

    return query;
}

Update:

From your latest comment I guess you turned off lazy loading for your model by setting LazyLoadingEnabled to false. By default lazy loading is enabled which can be a bit confusing since older versions of EF didn't even support lazy loading.

Disabling lazy loading "activates" explicit loading and unsurprisingly requires you to explicitly load related objects. In your case you can do this by either using the Include method (this is called eager loading):

var query = from a in _contextProvider.Context.Addresses.Include("State")
            where a.CityName.StartsWith("I")
            select a;

or access the related property in query (which modifies the final query):

var states = (from a in Addresses() // Addresses is your query method
              select a.State).ToList();

In the second version linq-to-entities automatically includes the states because you are accessing them in the query. Note that your Addresses method returns an IQueryable and therefore the query will not be executed before you actually enumerate it. So the executed SQL query very much depends on how you use the query returned by Addresses.

If you first execute the Addresses query and access the states later linq-to-entities will not include them:

var states = (from a in Addresses().ToList() // <- ToList() executes the query before states are accessed
              select a.State).ToList();

Lazy loading is often turned off to avoid unneeded roundtrips to the database. If you enable lazy loading and then immediately execute the query returned by Addresses each access to a related object will actually produce a database query. But again: if you include the states in the final query linq-to-entities will automatically include them by generating a JOIN (no matter if you use lazy loading or not).

So your solution to use Include to eagerly load related states (even if you don't access them later) is ok. And your original idea to use a join is also ok. But since you want your method to return an IQueryable<Address> you have to do it on the call site (as described above).

From my experience enabling lazy loading often makes things a lot easier. If you don't know exactly what's going on it can result in a lot of unnecessary roundtrips to the database. But you can still use Include to optimize your queries. To better understand how LINQ queries get translated into SQL statements I recommend to use SQL Profiler (or a free tool like Express Profiler if you use SQL Server Express).

On the other hand using explicit loading can also be a pain. Being forced to include all related tables can result in huge datasets. If for some reasons you can not include all related tables you have to explicitly check if an entity is loaded: context.Entry(address).Reference(a => a.State).IsLoaded. If a nullable property is null you don't know if it is NULL in the database or just not yet loaded.

Community
  • 1
  • 1
pescolino
  • 3,086
  • 2
  • 14
  • 24
  • That gives me a conversion error, (Cannot implicitly convert type 'System.Linq.IQueryable' to 'System.Linq.IQueryable'), and I understand why. I want all the Address fields, not just the state name. Do I have to explicitly list them? And then will I have to cast it? Thanks – mwill Oct 12 '13 at 02:59
  • @mwill: I changed the return type to `IQueryable`. If you want to get all Address fields then your code is perfectly ok. The JOIN is already done through the ForeignKey attribute of the State property. – pescolino Oct 12 '13 at 03:13
  • Yes, I want all the address fields. In my original code, the State is NULL so I thought I had to write the join myself. Maybe there's a setting that I'm missing somewhere? thanks – mwill Oct 12 '13 at 03:41
  • For me this looks perfectly ok. Make sure the database has a relation between both tables and of course make sure that there are related state records (i.e. StateId not set to NULL). – pescolino Oct 12 '13 at 04:06
  • I found a workaround, or it may actually be the solution. If I use ".Include("State")" then I get the state data returned in the address entities. – mwill Oct 12 '13 at 14:50
  • @mwill: Have a look at my update. I hope this explains what is going on. – pescolino Oct 13 '13 at 23:58