I have a customer table in the db that stores the name, address etc, along with some photo, logo and other images.
I am using entity framework and I would like to know how to tackle the situation where I only want to bring back basic data about the customer in certain situations vs the complete data including images.
Should I have two entities, CustomerBasic and CustomerComplete
OR
have one Customer entity and fill it with two separate methods, FillBasic, FillComplete.
Any best practices? i'm new to EF.
It really depends on your repository structure if any - with EF itself you can just use a projection with the properties you do want to a business object instance that represents a "basic customer", i.e.
var customerBasicList = context.Customers
.Where(...)
.Select( c => new CustomerBasic()
{
FirstName = c.FirstName,
LastName = c.LastName,
}).ToList();
Performance becomes more important when you have a collection property (i.e. images in your example) in your entity. In your case you can take advantage of lazy loading to only materialize those properties when and if needed.
I think what you want can be accomplished by projecting the query with the select clause. For example (taken from this article)
using(AdventureWorksDB aw = new
AdventureWorksDB(Settings.Default.AdventureWorks)) {
var newSalesPeople = from p in aw.SalesPeople
where p.HireDate > hireDate
orderby p.HireDate, p.FirstName
select new { Name = p.FirstName + " " + p.LastName,
HireDate = p.HireDate };
foreach(SalesPerson p in newSalesPeople) {
Console.WriteLine("{0}\t{1}", p.FirstName, p.LastName);
}
}
You can do a lot with this pattern.
Related
Using Linq to Entities I need to filter an my entity set based on a list of complex objects that represent criteria. For example:
var criteria = new[]
{
new { FirstName = "Bob", LastName = "Smith" },
new { FirstName = "Jane", LastName = "Doe" }
};
I am try to do the following:
var filtered = PersonEntities.where(person => criteria.Any(c => c.FirstName == person.FirstName && c.LastName == person.LastName)).ToList();
This results in an error due to the query builder not being able to process the complex object. I know that if I had an Id value to filter on, I could simply select it into an in-memory list and replace the complex object with it in the linq query criteria. However I have not been able to find a solution when multiple fields are required in the criteria. Any ideas?
As suggested in the comment, in this specific case we can work around the problem easily without having to build a complex Expression tree manually. If your criteria should be what you declared, you can always re-shape it to a list of strings containing firstname and lastname separated by a underscore (this should not be any of the valid characters for name). Then you have just primitive collection/array and can totally use it in the linq query, something like this:
var first_last = criteria.Select(e=> string.Format("{0}_{1}",e.FirstName,e.LastName));
var filtered = PersonEntities.Where(person => first_last.Any(e=>e == person.FirstName + "_" + person.LastName)).ToList();
If building the Expression tree, I guess you have to even touch the so-called ExpressionVisitor, it's quite a long code (at least 7-8 times of what we've done here).
Since I am using Entity Framework 6 Database First and my database has all of its relationships setup, my EDMX auto generates navigation properties for me whether they are object lists or individual objects. This way I can lazy load using .Include(x => x.NavProperty).
How would I handle something such as the following using lazy loading that was previously done in a stored procedure that would return a list of OrderDetailDto which has a string property called MaterialDescription?
SELECT od.*, IIF(m.Description is null, od.Description, m.description) as MaterialDescription
FROM OrderDetail od
LEFT OUTER JOIN Material m ON od.MaterialId = m.Id
I am really trying to avoid doing this but I might end up ignoring all of the build in navigation properties and create a Dto for each entity and strictly add the properties I want bound to it. Then do selects like the following:
using (var context = new AppContext())
{
var odList = (from od in context.MaterialPODetails
join m in context.Materials on od.MaterialId equals m.Id into gj
from m in gj.DefaultIfEmpty()
select OrderDetailDto
{
Id = od.Id,
MaterialPOId = od.MaterialPOId,
MaterialId = od.MaterialId,
Description = od.Description,
Unit = od.Unit,
Quantity = od.Quantity,
UnitPrice = od.UnitPrice,
Archived = od.Archived,
DateCreated = od.DateCreated,
CreatedBy = od.CreatedBy,
DateModified = od.DateModified,
ModifiedBy = od.ModifiedBy,
// navigation properties
MaterialDescription = (m == null ? obj.Description : m.Description)
}).ToList();
return odList;
}
}
Please give me any advice possible on how to avoid. I want to avoid creating Dtos and simply use the Auto Generated EF classes with the virtual properties but it seems that there is limitations and performance differences. I won't be able to do the If statement that I want to do with the left outer join.
I don't know if this is proper terminology but I believe what I want is to still use Flattened objects but not have to create Dtos.
I can perform a join using a navigation property, which to me is more DRY because I'm not repeating the join criteria everywhere:
(from c in db.Companies
from e in c.Employees
select new { Employee = e, Company = c}).ToList();
Because the ORM knows how Companies is related to Employees, the c.Employees navigation property is used to infer the FK criteria for the join.
What is the direct translation to extension/lambda syntax of a multiple from clause via navigation property?
I know there is a Join extension method, but requires you explicitly name the FK's being compared, rather than it imply that criteria by the navigation property. This is non-working but hopefully expresses my intent:
db.Companies
.Join(c.Employees, /* don't want to explicitly name FKs*/)
.Select(x => new { Employee = x.e, Company = x.c}).ToList();
Of course Join(c.Employees doesn't work because there is no c in this context, but idea being somehow use the Companies.Employees navigation property to imply the join criteria.
I know I can do:
db.Companies.Select(c => new { Employees = c.Employees, Company = c })
but that is a different result set, as it returns one record per company, and then the list of employees as a nested property. Rather than the first which is a join, thus there is a record for every related combination, and the result has a Employee property instead of a Employees collection.
I'm not sure, but guessing .SelectMany is the direct translation. You don't get a c reference to the parent, so if you do multiple of these:
db.Companies.SelectMany(c=>c.Employees).SelectMany(e=>e.VacationDays).Select(v => new { VacationDay = v, Employee = v.Employee, Company = v.Employee.Company })
You have to walk backwards across the relationships to flatten out the join. In linq it's much simpler because you would have c, e and v all in the context of the select. I don't know if you can express the same thing in extension methods, such that all three alias/references are passed down. Maybe just a consequence of the extension method syntax, but hoping someone will provide a better equivalent.
SelectMany is indeed what multiple from clauses are mapped into.
In order to keep variables in scope the projection each SelectMany needs to project the sequence into a new anonymous object that keeps all of the appropriate variables in scope:
var query = db.Companies.SelectMany(company => company.Employees,
(company, employee) => new
{
company,
employee
});
To add additional projections for additional nested navigation properties, simply repeat the pattern with a subsequent call to SelectMany:
var query = db.Companies.SelectMany(company => company.Employees,
(company, employee) => new
{
company,
employee
}).SelectMany(pair => pair.employee.VacationDays,
(pair, vactionDay) => new
{
pair.company,
pair.employee,
vactionDay,
});
See this blog post for some more details and an in-depth description of this transformation, and how it scales.
Wouldn't it just something like:
db.Employees.Select(m => new { Employee = m, Company = m.Company });
As each employee has a Company, why don't just add navigation property "Company" to Employee entity?
To get vacations, just change it to the following:
db.Employees.SelectMany(
employee => employee.VacationDays,
(employee, vacationDay) => new
{
Employee = employee,
Company = employee.Company,
VacationDay = vacationDay
});
Update:
Actually, there is no difference between:
(from c in db.Companies
from e in c.Employees
select new { Employee = e, Company = c}).ToList();
and:
(from e in c.Employees
from c in db.Companies
select new { Employee = e, Company = c}).ToList();
This has been bugging me for a while since I'm trying to come up with an optimized way of querying this.
So lets say I have 3 cross reference tables that share a common column, where that common column will do a final join on a main table that contains more info.
For example:
Let's say I have the following:
Customers //properties: ID, Name, Address
IEnumberable<CustomerSports> //properties: CustomerID, SportsID
IEnumberable<CustomerLocation> //properties: CustomerID, LocationID
IEnumberable<CustomerPets> //properties: CustomerID, PetsID
So I can make queries such as:
Give me a list of customers that plays lacrosse, football, soccer (CustomerSports)... and have dogs and cats (CustomerPets), that live in New York (CustomerLocation). The lookup tables can be nullable, so Customers could play sports, but have no pets.
Then when I get a list of customers, I'll join that common column (CustomerID) on the customer table to retrieve the ID, Name, and Address.
I was thinking about having the customer table join on each lookup, and then doing a union to fetch the list of customers, but I don't know if that is the correct way of doing it.
As long as you have setup your design correctly then each Customer should have a Sports collection, a Pets collection and a Locations (unless this last one is a one-to-one join?).
If those relationships are setup, then you can query as follows:
var sports = new string[] { "lacrosse", "football", "soccer" };
var pets = new string[] { "cat", "dog" };
var locations = new string[] { "new york" };
var sportyPetLoversInNewYors = db.Customers
.Where(cust => sports.All(sport => cust.Sports.Any(custSport => custSport.Name == sport)))
.Where(cust => pets.All(pet => cust.Pets.Any(custPet => custPet.Name == pet)))
.Where(cust => locations.All(loc => cust.Locations.Any(custLoc => custLoc.Name = loc)))
// could customise the select here to include sub-lists or whatever
.Select();
This assumes that you only want people that have all 6 criteria. If you want people that like at least one of those sports, with at least one of those pets, and (assuming you used more than one location) are in at least one of those locations, the Where expression would change like the following
.Where(cust => cust.Sports.Any(custSport => sports.Contains(custSport.Name)))
Let me know if you need further explanation.
One method of doing this, if i understood what you were after. Allows multiple sports, and multiple pets, or none.
var contacts = from cust in customer
join sport in sports on cust.CustomerID equals sport.CustomerID into multisport from sport in multisport.DefaultIfEmpty()
join loc in location on cust.CustomerID equals loc.CustomerID
join pet in pets on cust.CustomerID equals pet.CustomerID into multipet from pet in multipet.DefaultIfEmpty()
select new
{
cust.CustomerID,
multisport,
loc.LocationID,
multipet
};
I am trying to get the columns dynamically. In NHibernate i can do this:
var list = _session.CreateCriteria(typeof(Person))
.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("FirstName"))
.Add(Projections.Property("LastName"))
.Add(Projections.Property("Jersey"))
.Add(Projections.Property("FortyYard"))
.Add(Projections.Property("BenchReps"))
.Add(Projections.Property("VertJump"))
.Add(Projections.Property("ProShuttle"))
.Add(Projections.Property("LongJump"))
.Add(Projections.Property("PersonSchoolCollection"))
)
.SetResultTransformer(new NHibernate.Transform.AliasToBeanResultTransformer(typeof(Person)))
.List<Person>();
What is the equivalent in Linq?
As you also tag linq-to-sql and linq-to-entities I assume you are looking for an equivalent in Linq-to-Sql or Entity Framework. The two answers (so far) would be such equivalents if _session.Query<Person>() were replaced by context.Persons. (Although Darius's answer would throw an exception saying that you can't create entity instances in an entity query).
But apart from the possibility to use Select to create an ad hoc projection, one of the newer features of AutoMapper makes it even easier. Automapper is a very popular tool to map (say: project) a list of types to another list of types. But until recently its drawback was that it only worked on in-memory lists, i.e. the projection did not propagate into the SQL query. So it could not be used to cut back the number of queried fields (as NHibernate projections does).
This problem was described in Stop using AutoMapper in your Data Access Code (the title says it all). But it also offered a preliminary but excellent fix, which was later adopted by Automapper itself.
This feature makes it possible to write very succinct code like:
var dtoList = context.Persons.Project().To<PersonDto>();
(after the mapping between Person and PersonDto was registered in Automapper).
Now the SQL query only contains the fields that are used for PersonDto.
Isn't that would work:
_session.Query<Person>()
.Select(new {FirstName, LastName, Jersey, FortyYard})
.ToList()
.Select(x => new Person() {
FirstName = x.FirstName,
LastName = x.LastName,
Jersey = x.Jersey,
FortyYard = x.FortyYard
}
);
var list = from person in context.Persons
select new Person()
{
FirstName = person.FirstName,
LastName = person.LastName,
Jersey = person.Jersey,
FortyYard = person.FortyYard,
BenchReps = person.BenchReps,
VertJump = person.VertJump,
ProShuttle = person.ProShuttle,
LongJump = person.LongJump,
PersonSchoolCollection = person.PersonSchoolCollection
};