How to perform a left outer join using Entity Framework using navigation properties - entity-framework

I'm trying to find out how I would define the code first navigation properties on these two classes to perform something similiar to this query:
SELECT USERID, FIRSTNAME, LASTNAME, COURSEID, NAME
FROM User
LEFT OUTER JOIN Course ON User.USERID = Course.USERID
WHERE COURSEID = 1
So I'm trying to find a list of users together with if they have attended a certain course.
public class User
{
public int UserId {get;set; }
public string FirstName {get;set;}
public string LastName {get;set;}
public virtual ICollection<Course> Courses { get; set; }
}
public class Course
{
public int CourseId { get;set; }
public int UserId { get;set; }
public string Name { get;set; }
public virtual User User {get;set;}
}
If I was to write a query to achieve this
var u = Users.Where(x => x.Courses.Any(x => x.CourseId = 1));
This does a subquery, which is not what I wanted (as people who didnt attend the course would not show).
How would the navigation property be defined?
HasMany(t => t.Courses).WithOptional(t => t.User).HasForeignKey(t => t.UserId);

Check this link:
http://msdn.microsoft.com/en-us/library/vstudio/bb397895.aspx
Left outer joins in LINQ are done via DefaultIfEmpty method.

var u = Users.Select ( x => new {
User = x,
AttendedCourse = x.Courses.Any()
} );
For specific course id,
var u = Users.Select ( x => new {
User = x,
AttendedCourse = x.Courses.Any( c => c.CourseID == 1 )
} );
Sub query is the only way to write related queries, However, EF will choose the best suitable join type and will give you correct results. And EF can manage mostly all kinds of queries without doing joins.

Related

Using Expression to return selective columns on entities having multiple objects

I have an application where I have a model composed of several other objects. For instance:
class Customer
{
public int CustomerId{get;set;}
public int? AddressId {get;set;} // this is set as allow null in database
public string Name {get;set}
public virtual Addresss Address {get;set;}
}
class Address
{
public int AddressId {get;set}
public string A1 {get;set}
}
The idea is to use context.customers.include("Address"). However the model I am currently working on is much more complex than the above.
I have used https://stackoverflow.com/a/51772067 as a reference, but unfortunately this does not work for an id having no value (nullable, as the database allows nulls)
How can I modified the expression to behave as a true left join (includes an empty entity if the id is null).
Thanks in advance for your assistance
As per the official doc you could do left join as below
List<Person> people = new List<Person> { magnus, terry, charlotte, arlene };
List<Pet> pets = new List<Pet> { barley, boots, whiskers, bluemoon, daisy };
var query = from person in people
join pet in pets on person equals pet.Owner into gj
from subpet in gj.DefaultIfEmpty()
select new { person.FirstName, PetName = subpet?.Name ?? String.Empty };
When you use DefaultIfEmpty() then it becomes a left join.
Upvote if it works.

Child List Element Gets Only 1 Record in Entity Framework

In Entity Framework, I would like to get one object which includes a list, but list gets only first record.
I have 2 objects Sale and Profile, they are different from database objects, I create these objects in query like "select new Sale { }". Profile object contains Sale type list. When query executed, list gets just first record in database.
Sale Complex Object
public class Sale
{
public int Id { get; set; }
public string Header { get; set; }
public double Price { get; set; }
}
Profile Complex Object
public class Profile
{
public int Id { get; set; }
public string Name { get; set; }
public List<Sale> SalesList { get; set; }
}
I use left join because it should insert this object to list, if next object is null.
Query Here
Profile profile = (from u in db.USER
join s in db.SALE on u.ID equals s.USER_ID into saleleft
from salej in saleleft.DefaultIfEmpty()
where u.ID == _userId
select new Profile
{
Id = u.ID,
Name = u.NAME,
SalesList= new List<Sale>()
{
salej != null ? new Sale
{
Id=postj.ID,
Header=salej.HEADER,
Price=salej.PRICE
} : null
}.ToList()
}).FirstOrDefault();
I guess this can be about FirstOrDefault() method. Hence I think it should get all records to SalesList. How can I get all records to list? Any idea?
Thanks in advance.
I think you need to use group here. Could you try this and let me know if it works?
// didn't test the code
Profile profile = (from u in db.USER
join s in db.SALE on u.ID equals s.USER_ID into saleleft
where u.ID == _userId
from salej in saleleft.DefaultIfEmpty()
group salej by new { u.ID, u.NAME } into g
select new Profile
{
Id = g.Key.ID,
Name = g.Key.NAME,
SalesList = g.Select( x => new Sale { Id = postj.ID, Header = x.HEADER, Price = x.PRICE }).ToList()
}).FirstOrDefault();
Btw, what is postj?

Inlining collections with EF Projection

I have the following classes:
public class Customer {
public int Id {get;set;}
public string Name {get;set;}
public List<Order> Orders {get;set;}
//other attributes
}
public class Order{
public int Id {get;set;}
public string Name {get;set;}
public decimal Value {get;set;}
}
Given a customerId I wish to only select the customer name and the order Id using projection in EF.
I am doing the following:
IQueryable<Customer> customer = DataContextFactory.GetDataContext().Set<Customer>();
var tempCustomer = customer.Where(x => x.Id == customerId).Select( c=>
new
{
Name = c.Name
}
)
This gives me the customer name. Which I can then pass back to the entity like so:
var customerToReturn = tempCustomer.ToList().Select(x => new Customer
{ Name = x.Name});
If I include the order on the query like this:
var tempCustomer = customer.Where(x => x.Id == customerId).Select( c=>
new
{
Name = c.Name,
Orders = new {
Id = c.Orders.Id
}
}
)
Then I get a duplicate customer per order line (as per the SQL generated by EF). Is there a way I can inline this generation into a single SQL call?
Currently I am getting around this by calling each child object separately.

Automapper MapFrom Subquery

UPDATE: Issue fixed in current release https://github.com/AutoMapper/AutoMapper/issues/742
Using AutoMapper 3.3, QueryableExtensions and EF6
I have a user requirement to return a Count of other users created before the current user.
I have the following
public class User
{
public int Id {get;set;}
public string Name { get; set; }
public DateTime? DateActivated {get;set;}
}
public class UserViewModel
{
public int Id {get;set;}
public string Name { get; set; }
public DateTime? DateActivated {get;set;}
public int position {get;set;}
}
public class AutoMapperConfig
{
public static void ConfigAutoMapper() {
var db = new DB();
Mapper.CreateMap<User, UserViewModel>()
.ForMember(a => a.position, opt => opt.MapFrom(src => db.Users.Where(u => u.DateActivated < src.DateActivated).Count()));
Mapper.AssertConfigurationIsValid();
}
}
and finally the actual mapping:
user = db.Users.Project().To<T>(new { db = db }).FirstOrDefault(a => a.id == id);
db is a local DbContext variable and I'm using AutoMapper parameters to insert it into the mapper (https://github.com/AutoMapper/AutoMapper/wiki/Queryable-Extensions#parameterization)
So far so good, this compiles and runs, but the result for user.position is 0
I checked with sql profiler and here is the relevant section of the generated query:
CROSS JOIN (SELECT
COUNT(1) AS [A1]
FROM [dbo].[Users] AS [Extent4]
WHERE ([Extent4].[DateActivated] < [Extent4].[DateActivated]) ) AS [GroupBy1]
Notice how it refers to Extent4.DateActivated in both sides of the comparison, which will obviously yield 0 results.
So is what i'm doing just not possible? or did I do something wrong.
(and if I could do away with the parameterization and have automapper be able to refer to the current underlying db context that would be a bonus).
Thank you
EDIT
Just to make it clear, this count will be dynamic, since there are other criteria to filter prior users that I omitted from simplified the example.

Searching the Entity Framework domain model utilising Code First

Got a very difficult EntityFramework Code First question. I'll keep this as simple as possible.
Imagine we have n number of classes, lets start with 2 for now
public class Person
{
public string Name { get; set; }
}
public class Address
{
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
}
Now then, what I want to do is be able to search the domain model with a single string, i.e. something like DbContext.Search( "Foo" ). The call would search both the person and address tables for a string match and would return a list populated with both Person and Address entities.
Have to say I am not entirely clear how to go about it but I am considering using DataAnnotations to do something like this
public class Person
{
**[Searchable]**
public string Name { get; set; }
}
public class Address
{
**[Searchable]**
public string AddressLine1 { get; set; }
**[Searchable]**
public string AddressLine2 { get; set; }
}
Am I on the right track?
Should I use the Fluent API instead?
Reflection?
Any and all thoughts massively appreciated.
the Find method searches only in the Primary Key column. If we don't make any column explicitly primary key column then find method will throw error. Generally EF convention takes propertyName+id as the primary key in the class. But if you want to search with Name then Make add [Key] to the property. it will become primary key and u will be able to find properties.
dbContext.Addresses.find("Foo");
Create a new object type onto which you'll project 2 types of search results:
public class Result
{
public string MainField { get; set; }
// you may have other properties in here.
}
Then find entities of each type that match your criteria, projecting them onto this type:
var personResults = DbContext.Persons
.Where(p => p.Name == "Foo")
.Select(p => new Result{MainField = p.Name});
// don't forget to map to any other properties you have in Result as well
var addressResults = DbContext.Adresses
.Where(a =>
a.AddressLine1 == "Foo" ||
a.AddressLine2 == "Foo"
).
.Select(a => new Result{MainField = a.AddressLine1 + ", " + a.AddressLine2 });
// again, don't forget to map to any other properties in Result
Then merge the lists:
var allResults = personResults.Union(addressResults).ToList();
...at which point you can sort the list however you like.
"Result" and "MainField", are rather generic; just using them because I am not thoroughly aware of your domain model.