Best practice to merge Table Entity with View Entity - c#

Is there any better way to merge a Entity Table with a Entity View
A little example: I have a Person table with:
id,
name,
lastname
columns and a view named ViewPersonLastLocations with:
person_id
location_name.
I need to display Person table with the information of ViewPersonLastLocations.
Actually i can "merge" those entities with two foreachs, and i create a variable in the Person partial class.
Is there any other way to do this?

I am a little unclear on what you want based on your last comment, but will start with this code for a join if the relationship is 1:1. If it is 1:Many, then it is similar, but project into a collection.
var personWithLocation = context.Persons
.SelectMany(p => context.ViewPersonLastLocations
.Where(vp => vp.person_id == p.id)
.DefaultIfEmpty(),
(p, vp) => new PersonViewModel // create a viewmodel for results or anonymous
{
Id = p.id,
Name = p.name,
LastName = p.lastname,
LocationName = vp.location_name
}
).ToList();

Related

The Include path expression must refer to a navigation property defined on the type (select data using LINQ)

I was trying to select data using LINQ
and I have a list called "products" and I want just these items that exist in products list
var Owner = db.Owners
.Where(m => m.ID == id)
.Include(m => m.Products.Where(item1 => products.Any(item2 => item2.ProductID == item1.ProductID)).ToList())
.FirstOrDefault();
but I'm getting this error :
System.ArgumentException: 'The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
Parameter name: path'
Include is meant to fetch complete rows of a table, inclusive primary and foreign keys.
Usually it is not efficient to fetch the complete rows of a table. For example, suppose you have a database with Schools and Students. There is a one-to-many relation between Schools and Students: every School has zero or more Students, every Student attends exactly one School, namely the School that the foreign key refers to.
If you fetch School [10] with its 2000 Students, then every Student will have a foreign key SchoolId with a value 10. If you use Include and fetch complete Student rows you will transfer this value 10 over 2000 times. What a waste of processing power!
A DbContext has a ChangeTracker object. Whenever you fetch data without using Select, so if you fetch complete rows, then the fetched rows are stored in the ChangeTracker, together with a Clone of it. You get a reference to the Clone (or the original, doesn't matter). When you change properties of the fetched data, you change the value in the Clone. When you call SaveChanges, the values of all properties of all originals in the ChangeTracker are compared with the values in the Clones. The items that are changed are updated in the database.
So if you fetch School [10] with its 2000 Students, you are not only fetching way more data than you will ever use, but you will also store all these Students in the ChangeTracker together with a Cloned Student. If you call SaveChanges for something completely different (change of the telephone number of the School for instance), then all Students are compared by value property by property with their Clones.
Generic rule:
Whenever you fetch data using Entity Framework, always use Select, and Select only the properties that you actually plan to use. Only fetch complete rows and only use Include if you plan to update the fetched data.
Using Select will also solve your problem:
int ownerId = ...
IEnumerable<Product> products = ...
var Owner = db.Owners.Where(owner => owner.ID == ownerId)
.Select(owner => new
{
// Select only the Owner properties that you actually plan to use
Id = owner.Id,
Name = owner.Name,
// get the Products of this Owner that are in variable products
Products = owner.Products
.Where(product => products.Any(p => p.ProductId == product.ProductId)
.Select(product => new
{
// Select only the Product properties that you plan to use
Id = product.Id,
Price = product.Price,
...
// No need to fetch the foreign key, you already fetched the value
// OwnerId = product.OwnerId,
})
.ToList(),
...
})
.FirstOrDefault();
I used automatic types (new {...}). If you really want to create Owner and Properties, use:
var Owner = db.Owners.Where(...)
.Select(owner => new Owner
{
Id = owner.Id,
...
Products = owner.Products.Where(...).Select(product => new Product
{
Id = product.Id,
...
})
.ToList(),
})
.FirstOrDefault();
Try the following:
var productIds = products.Select(x => x.ProductID);
var Owner = db.Owners
.Where(m => m.ID == id)
.Include(m => m.Products.Where(product => productIds.Contains(product.ProductID))
.FirstOrDefault();

Entity Framework LINQ equivalent of TSQL to include count of child records

Using Entity Framework and LINQ, how might I achieve this TSQL:
SELECT Children.ChildCount, Parent.*
FROM Parent
LEFT JOIN (SELECT ParentID, COUNT(ChildID) AS ChildCount FROM Child GROUP BY ParentID) AS Children
ON Parent.ID = Children.ParentID
Note that this is just a small part of what is already a larger LINQ query that includes other related entities so using a RawSQL query is not an option. Also the Parent table has around 20 columns and I'm hoping not to have to specify each individually to keep the code maintainable.
EDIT: To clarify a couple of things, the model for the output of the query (very simplified) looks something like this:
public class MyEntity
{
public int ID {get; set;}
public string Name {get; set;}
public int ChildCount {get; set;}
// many other properties here including related records
}
So what I'm trying to do is get the ChildCount included in the result of the query so it is included in the EF entity.
You can use a Select Query to project the DB info onto an entity, something like:
var entity = db.Parent.Select(x =>
new MyEntity
{
Id = x.Id,
Name = x.Name,
ChildCount = x.Children
.Select(y => y.ParentId == x.Id)
.Count()
})
.SingleOrDefault(x => x.Id == IDYouNeedToQuery);
}
What this should do is return you 1 instance of your MyEntity class with the Name, ID, and ChildCount properties filled in. Your SQL won't quite match what is generated but this should get you what you want. BTW you can also sub the SingleOrDefault line with a filter of another type, or no filter in which case the entity variable becomes a collection of MyEntity.
For further reading on this technique and how to use AutoMapper to make it super easy to set up, check out this post from Jon P Smith, who literally wrote the book on Entity Framework Core.
Hope this helps.
For anyone who comes across this at a later date, I ended up using AutoMapper as suggested by Nik P (I was already using AutoMapper anyway) to map from db entities to DTOs.
In effect, in my AutoMapper mapping I have:
CreateMap<MyEntity, MyEntityDTO>()
.ForMember(d => d.ChildCount, opt => opt.MapFrom(src => src.ChildEntity.Count()))
Then in my Service layer I call:
IEnumerable<MyEntityDTO> results = await dbContext.MyEntities.ProjectTo<MyEntityDTO>(mapper.ConfigurationProvider).ToListAsync();

Simple Linq to Sql

If have the following table structures
Key Site Building Person Grade
Primary key SiteId BuildingId PersonId GradeId
Foreign key SiteId BuildingId
GradeId
Knowing the SiteId, I am looking to obtain a list of Person objects which have a set grade.
I am using the uof and repository patterns and the code used to pull out the data is as follows:
var site = unitOfWork.Repository<Site>()
.Query(si => si.SiteId == siteId)
.Select()
.FirstOrDefault();
What I would like to do is something like:
List<Person> person = Person.Where(p => p.Site.SiteId == 99
&& p.Site.Building.person.Grade == 3);
but I cannot see site from person or grade objects in intellisence.
I have tried to include the tables, but again, intellisence does not allow me to dig deeper then the next object, so
var site = unitOfWork.Repository<Site>()
.Query(si => si.SiteId == siteId)
.Include(si => si.Building)
.Include(si => si.Building.Person) ..... not visible
.Select()
.FirstOrDefault();
What do i need to change to either enable me to:
pull back the extra table objects in the initial query
traverse the objects using intellisence
using existing code to pull a person object
var person = Person.Where(p => p.Building.SiteId == 99 && p.GradeId == 3).ToList();

Best way of handling views

I want to start that new project with the best practice possible. I have a lot of tables that are linked together. Example:
Person
- ID
- FirstName
- LastName
User
- ID
- PersonID
- AddressID
Address
- ID
- Line1
- Line2
Obviously, I will have to use a UserView view to be efficient in the application. The view will bring these three tables together.
Now, when someone makes a change to a user, I will have to query all three tables individually, make the changes and then update.
private void updateUser(UserView userView)
{
using (var context = getNewContext()
{
var person = context.Person.first(c => c.ID == userView.PersonID)
person.FirstName = userView.FirstName;
person.LastName = userView.LastName;
context.SaveChanges();
var user = context.User.first(c => c.ID == userView.UserID)
//and so on for the 3 tables
}
}
There has to be a more efficient way of updating the tables! Do you know any trick?
I assume you're using the entity framework. If you've got navigation properties you can eagerly load your object graph in one trip to the database, update the fields and then save it.
var user = context.User.Include("Person").Include("Address").First(c => c.ID == userView.UserID);
user.Person.FirstName = userView.FirstName;
user.Person.LastName = userView.LastName;
user.Address... // etc.
If you're using Linq2Sql, you've got to use DataLoadOptions. There's a blog post explaining the difference between EF and Linq2Sql.

Query common contacts (self many to many relationship) in entity framework

I have this classic scenario where I have a User table and a Contact table containing only UserId and ContactId columns (so it is a self many to many relationshsip). What I would like is a query that gives me a list of userIds with number of common contacts with the specified User. In plain old SQL I have the following query (contacts of user and user itself is filtered out to get facebook like friend suggestions):
SELECT COUNT(c1.ContactId) as CommonContact, c2.UserId
from Contacts as c1
inner join Contacts as c2 on c1.ContactId = c2.ContactId
Where c1.UserId = #Id AND c2.UserId != #Id
AND c2.UserId NOT IN (SELECT ContactId from Contacts Where UserId = #Id)
Group By c2.UserId
ORDER BY CommonContact Desc
This simple query works great but I can not figure out how to write the same query in LINQ to Entity, because in the Entity Framework model I have User entity that entity have Contact navigation property but the connection table is not there directly....
Thanks a lot for any help...
Didn't have time and try to run it but something like this should work.
public class Test
{
//simulate an IQueryable
private readonly IQueryable<Person> _people = new List<Person>().AsQueryable();
public void FindContactMatchCount(Guid personId)
{
//we'll need the list of id's of the users contacts for comparison, we don't need to resolve this yet though so
//we'll leave it as an IQueryable and not turn it into a collection
IQueryable<Guid> idsOfContacts = _people.Where(x => x.Id == personId).SelectMany(x => x.Contacts.Select(v => v.Id));
//find all the people who have a contact id that matches the selected users list of contact id's
//then project the results, this anonymous projection has two properties, the person and the contact count
var usersWithMatches = _people
.Where(x => idsOfContacts.Contains(x.Id))
.Select(z => new
{
Person = z, //this is the person record from the database, we'll need to extract display information
SharedContactCount = z.Contacts.Count(v => idsOfContacts.Contains(v.Id)) //
}).OrderBy(z => z.SharedContactCount)
.ToList();
}
}

Categories

Resources