What is the best approach at the repository level to hydrate an object's sub entities. For example: I need a list of all people in the system. Each person can have many phone numbers. How would I go about filling all this data in my repository?
public class Person
{
public int? Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
public List<Phone> PhoneNumbers { get; set; }
}
public IEnumerable<Person> GetPeople()
{
var persons = new List<Person>();
const string sql = "SELECT FirstName, LastName, MiddleName FROM Person.Person";
while (reader.Read())
{
var p = PersonFactory.GetPerson(reader);
p.PhoneNumbers = this.GetPhoneByPersonId(p.Id);
persons.Add(p);
}
return persons;
}
public IEnumerable<Phone> GetPhoneByPersonId(int? id)
{
while (reader.Read())
{
numbers.Add(PersonFactory.GetPhone(reader));
}
}
My problem with this approach is that I'm executing a bunch of sql statements that hit the DB. That just doesn't seem like the best approach. I cannot use any frameworks and must use a DataReader.
What is a better approach I could take? Also this is a simple example. There will be many more lists with my Person object.
One approach to implementing the GetPeople method would be execute a single SQL statement containing multiple result sets. If using SQL Server with MARS enabled, this will come from the server as a response to a single query. Otherwise, you can execute two queries, one to retrieve the people and another to retrieve all phone numbers that would be associated with those people.
Your queries can look something like:
SELECT FirstName, LastName, MiddleName FROM Person.Person WHERE -- omitted
SELECT PhoneNumber,Name FROM Person.PhoneNumbers WHERE -- restrict to same set as above
Again, you can either send both queries in a single command if the server supports it, or issue two separate queries. Either way, that is only two queries, which is no big deal. Do make sure that you aren't returning too many results at once.
Once you have the two result sets in memory, you can join them by person ID and associate all people instances with their corresponding phone numbers. Overall, you are doing either one or two database queries.
The IDataReader provides the method NextResult to enumerate multiple result sets returned by the query.
Related
I want to retrieve only one column of my data base. The following code works truly:
This is my customer in mysql-table/model-in-EF6
public partial class customers
{
public customers()
public int CustomerID { get; set; }
public string FullName { get; set; }
public string Mobile { get; set; }
public string Email { get; set; }
public string Address { get; set; }
public string Image { get; set; }
}
public List<customers> GetAllCustomers()
{
return myContext.customers.ToList();
}
This is my question:
var GetOneColumn = myContext.CustomerRepository.GetAllCustomers().Select(f=>f.FullName);
Does it retrieve all columns from customers in database and then select only one column (FullName) from retrieved data or not, it retrieve just only one column(FullName) from data base? If it retrieve all data from data base what is the correct code (Linq)?
How could I find that??
Since you're using a .ToList() EF will
Retrieve all customers from database
Map them to customer objects
Later, when you compute GetOneColumn, you do a projection on them (iterating through already materialized object list)
To retrieve only one column,
Remove the .ToList() from the repository, and return a IQueryable<Customers>
Call a .ToList() after your select var GetOneColumn = myContext.CustomerRepository.GetAllCustomers().Select(f=>f.FullName).ToList();
So, your code would be
public IQueryable<customers> GetAllCustomers()
{
return myContext.customers;
}
// later in code
var GetOneColumn = myContext.CustomerRepository.GetAllCustomers().Select(f=>f.FullName).ToList();
See what's going on for yourself! Break up your code into steps and debug:
var allCustomers = myContext.CustomerRepository.GetAllCustomers();
var allCustomerNames = allCustomers.Select(f=>f.FullName);
Alternatively, run a profiler on your DB, or enable logging of queries in EF
To see all the queries that EF is generating you can do something like this
using (var context = new BlogContext())
{
context.Database.Log = Console.Write;
// Your code here...
}
See more details in the docs and Log Queries executed by Entity Framework DbContext
If you read this far, then it's worth knowing what will actually cause EF to send a query - see How Queries Work
Basically, this happens whenever you start enumerating through the elements of an IQueryable<T> (including LINQ methods like First(), Last(), Single(), ToList(), ToArray(), and the like)
All of my DAL functions are using dbContext.Database.SqlQuery to map stored procedure results in business logic objects.
My application became more complicated and I'm looking for a modern, "up to date" way to handle the following situations. I know that I can achieve this using the low-level ADO.NET component like SqlDataReader and map the result manually, but I am sure there is the best way to do so using Entity Framework 6.
To the question: with this command dbContext.Database.SqlQuery<MyClass>, I can not handle:
The stored procedure that returns 2 result sets
Mapping the result set to a complex datatype
Example:
public class Order
{
public Customer customer { get; set; }
public Items[] items { get; set; }
}
Again, I know that I can map it manually or with AutoMapper, but I'm looking for an "up to date" approach based on Entity Framework 6.
Yes, there's a way using Translate.
Adapted from the official documentation:
var cmd = dbContext.Database.Connection.CreateCommand();
cmd.CommandText = "[dbo].[GetAllCustomersAndOrders]";
dbContext.Database.Connection.Open();
// Run the sproc
var reader = cmd.ExecuteReader();
var Customers= ((IObjectContextAdapter)dbContext)
.ObjectContext
.Translate<Customer>(reader, "Customers", MergeOption.AppendOnly);
reader.NextResult();
var Orders = ((IObjectContextAdapter)db)
.ObjectContext
.Translate<Order>(reader, "Orders", MergeOption.AppendOnly);
As far as the problem of mapping
few columns from the result to a 2nd level complex type? for example:
SELECT FirstName, LastName, OrderId FROM Orders I want to map it to:
public class Order { public Customer customer { get; set; } public int
OrderId { get; set; } }
The best would be to use a CustomerId inside your Order table, referencing a Customer table, instead of FirstName/LastName. It would be a good refactoring, normalizing the database. Otherwise you will not have a real mapping between your objects and your database, since your Order object will have a Customer property that doesn't exist in your database. In that case, you will have to create a class, e.g. NormalizedOrder
public class NormalizedOrder {
int OrderId { get; set; };
Customer OrderCustomer { get; set; };
}
And then, after the code above where you retrieve all Orders, do something like
var NormalizedOrders = Orders.Select new Order(){OrderId = e.OrderId, OrderCustomer = new Customer(){FirstName=>e.FirstName,LastName=>e.LastName}};
I have an entity like this
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public int Id { get; set; }
}
And have a stored procedure as
CREATE PROCEDURE [dbo].[GetPersons]
AS
SELECT Id,FirstName FROM [dbo].[Persons]
When I call this stored procedure in My DbContext
var dataResult1 = dbContext.SqlQuery<Person>("[dbo].[GetPersons]");
The data reader is incompatible with the specified '..'. A member of the type, 'LastName', does not have a corresponding column in the data reader with the same name
I know if I define a new entity that has Id, FirstName and map stored procedure result to it everything is worke.
Now is there any way that I map my stored procedure result to Person Entity without define a new entity?
You could have you query look like this:
CREATE PROCEDURE [dbo].[GetPersons]
AS
SELECT Id, FirstName, '' as LastName, 0 as Age FROM [dbo].[Persons]
You aren't pulling them from the DB although they do still go across the network.
However you are now using Person to represent two different things, and this is almost always a bad idea. I think you are better off with two separate objects and maybe create and interface on Id and FirstName if you have code that needs to work with both.
I also wonder what you are doing that pulling the two extra columns has been identified as being a performance bottleneck, what is the difference between pulling and not pulling the columns? Or is it a premature optimization?
You have options (though I don't understand the purpose):
You could simply create a new Entity class that would only map those
2 columns.
You could use dynamic as the type (then you would lose
intellisense on the result set at least).
Instead of an SP you could
create that as an inline table valued function:
CREATE FUNCTION [dbo].[GetPersons] ()
RETURNS TABLE AS RETURN
(
SELECT Id,FirstName FROM [dbo].[Persons]
);
Then your code could simply look like this:
var dataResult1 = dbContext.SqlQuery<Person>(#"Select Id, FirstName,
'' as LastName, 0 as Age
FROM [dbo].[GetPersons]()");
OTOH an SP like this is questionable in the first place.
I am using PetaPoco as an ORM and want to combine 2 POCO into 1 class and then execute CRUD operations with this class.
Right now I got something like this:
*Table Person has an fk address_id.*
public class Person
{
public PersonPOCO Person { get; set; }
public AddressPOCO Address { get; set; }
public Person(string sql)
{
Person = Db.SingleOrDefault<PersonPOCO>(sql);
Address = Db.SingleOrDefault<PersonPOCO>("SELECT * FROM addresses WHERE id = #0, PersonPoco.address_id");
}
public void Save()
{
var addressId = Db.Save(Address); // Returns inserted ID
Person.address_id = addressId;
Db.Save(Person);
}
}
This is working fine so far.
But it gets really annoying and repetive doing this for every needed Combination.
Especially saving is a pain, since I have to map the inserted ID to the dependent object.
Are there better ways to achieve this?
PetaPoco is designed to be fast and lightweight, so, you won't find this kind of complex mapping like Linq-to-sql or EF have in it.
I have a problem trying to get the count out of the following query:
var usersView = PopulateUsersView(); //usersView is an IQueryable object
var foo = usersView.Where(fields => fields.ConferenceRole.ToLower().Contains("role"));
Where UsersView is a class which is populated from an EF entity called users (refer to the first line in the code above)
This is the class definition for the UsersView class:
public class UsersView
{
public int UserId { get; set; }
public string Title { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Street1 { get; set; }
public string Street2 { get; set; }
public string City { get; set; }
public string PostCode { get; set; }
public string CountryName { get; set; }
public string WorkPlaceName { get; set; }
public string Gender { get; set; }
public string EMail { get; set; }
public string Company { get; set; }
public string RoleName { get; set; }
public string ConferenceRole { get; set; }
}
As I said trying to execute the line foo.Count() returns Null Exception and this might be because the ConferenceRole column allows Null in the database.
Now what I can't understand is that when I invoke the same query directly on the ObjectQuery the Count of records (i.e. invoking foo2.Count()) is returned without any exceptions.
var foo2 = entities.users.Where(fields => fields.ConferenceRole.ToLower().Contains("role"));
Is it possible to the same query above but using the IQueryable usersView object instead?
(It is crucial for me to use the usersView object rather than directly querying the entities.users entity)
EDIT
Below is the code from the PopulateUsersView method
private IQueryable<UsersView> PopulateUsersView()
{
using (EBCPRegEntities entities = new EBCPRegEntities())
{
var users = entities.users.ToList();
List<UsersView> userViews = new List<UsersView>();
foreach (user u in users)
{
userViews.Add(new UsersView()
{
UserId = u.UserId,
Title = u.Title,
Name = u.Name,
Surname = u.Surname,
Street1 = u.Street1,
Street2 = u.Street2,
City = u.City,
PostCode = u.Post_Code,
CountryName = u.country.Name,
WorkPlaceName = u.workplace.Name,
Gender = u.Gender,
EMail = u.E_Mail,
Company = u.Company,
RoleName = u.roles.FirstOrDefault().Name,
ConferenceRole = u.ConferenceRole
});
}
return userViews.AsQueryable();
}
}
Thanks
UPDATE...
Thanks guys I finally found a good answer to the difference between the IQueryable and the ObjectQuery objects.
As a solution I am checking if the ConferenceRole is null and then checking with the contains method as many of you guys have said.
My guess is that your PopulateUsersView() method is actually executing a query and returning an IQueryable Linq-to-Objects object - while the foo2 line executes the query only in the SQL layer. If this is the case, the obviously PopulateUsersView() is going to be quite an inefficient way to perform the Count
To debug this:
can you post some code from PopulateUsersView()?
can you try running both sets of code through the EF tracing provider to see what is executed in SQL? (see http://code.msdn.microsoft.com/EFProviderWrappers)
Update
#Ryan - thanks for posting the code to PopulateUsersView
Looks like my guess was right - you are doing a query which gets the whole table back into a List - and its this list that you then query further using Linq2Objects.
#ntziolis has provided one solution to your problem - by testing for null before doing the ToLower(). However, if your only requirement is to Count the non-empty items list, then I recommend you look at changing the PopulateUsersView method or changing your overall design. If all you need is a Count then it would be much more efficient to ensure that the database does this work and not the C# code. This is espeically the case if the table has lots of rows - e.g. you definitely don't want to be pulling 1000s of rows back into memory from the database.
Update 2
Please do consider optimising this and not just doing a simple != null fix.
Looking at your code, there are several lines which will cause multiple sql calls:
CountryName = u.country.Name
WorkPlaceName = u.workplace.Name
RoleName = u.roles.FirstOrDefault().Name
Since these are called in a foreach loop, then to calculate a count of ~500 users, then you will probably make somewhere around 1501 SQL calls (although some roles and countries will hopefully be cached), returning perhaps a megabyte of data in total? All this just to calculate a single integer Count?
Try to check whether ConferenceRole is null before calling a method on it:
var foo = usersView.Where(fields => fields.ConferenceRole != null
&& fields.ConferenceRole.ToLower().Contains("role"));
This will enable you to call the count method on the user view.
So why does it work against the ObjectQuery?
When executing the query against the ObjectQuery, LinqToSql is converting your query into proper sql which does not have problems with null values, something like this (it's sample markup sql only the actual query looks much different, also '=' is used rather than checking for contains):
SELECT COUNT(*) from USERS U WHERE TOLOWER(U.CONFERENCEROLE) = 'role'
The difference to the :NET code is: It will not call a method on an object but merely call a method and pass in the value, therefore no NullReference can occur in this case.
In order to confirm this you can try to force the .NET runtime to execute the SQL prior to calling the where method, by simply adding a ToList() before the .Where()
var foo2 = entities.users.ToList()
.Where(fields => fields.ConferenceRole.ToLower().Contains("role"));
This should result in the exact same error you have seen with the UserView.
And yes this will return the entire user table first, so don't use it in live code ;)
UPDATE
I had to update the answer since I c&p the wrong query in the beginning, the above points still stand though.