Having just figured out how to populate my ViewModel from a model using Automapper, I am now onto the next challenge – populating the ViewModel properties from a joined table.
The image below depicts my simple database.
My ViewModel class is defined as:
public class PersonViewModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Nullable<int> Age { get; set; }
public Nullable<int> SuffixId { get; set; }
public string PersonStatus { get; set; }
public string PersonPreference { get; set; }
public virtual Suffix Suffix { get; set; }
} enter code here
Note the additional fields of PersonStatus and PersonPreference that will come from joining PersonInfo to Person.
Here is the code I use to instantiate my mapping configuration:
Mapper.CreateMap<PersonViewModel, Person>();
Mapper.CreateMap<Person, PersonViewModel>();
And now the code to populate the ViewModel
List<PersonViewModel> persons = null;
using (var context = new DemoEntities())
{
persons = (from p in context.People.AsEnumerable()
join i in context.PersonInfoes on p.Id equals i.PersonId
select Mapper.Map<PersonViewModel>(p)).ToList();
return persons;
}
How would I populate the two joined properties (PersonStatus and PersonPreference) from this setup?
Thanks in advance!
AutoMapper can automatically flatten an object structure if the source and target classes meet some requirements.
The source class, the class that the mapping starts from, should have reference property to the related class. In you case: Person.PersonInfo (1:1, but n:1 will also work).1
The target class should contain property names that allow AutoMapper to resolve the property path to the referenced object. in your case: PersonViewModel.PersonInfoStatus, so AutoMapper will see that there's a property PersonInfo in the source object and a property Status in the referenced object.
If you've got these things in place, you can do
persons = context.People.Project().To<PersonViewModel>().ToList();
The Project().To syntax is a relatively new API that allows working on IQueryables. For more details see Does AutoMapper support Linq?.
You will see that this sends a JOIN query to the database.
1AutoMapper can also aggregate/flatten associated collections, but that's a different chapter.
Related
Note: These classes are related, but not part of the same Aggregate (like PurchaseOrder and OrderLine) - so I do not have a navigation property from "One" to "Many".
=== Entities ===
public class One
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class Many
{
public Guid Id { get; set; }
public string Name { get; set; }
public Guid One { get; set; }
}
=== Contracts ===
public class OneWithMany
{
public Guid Id { get; set; }
public string Name { get; set; }
public IEnumerable<Many>? ManyRelatedObjects { get; set; }
}
I want to select all One objects and any related Many objects from DbSet/DbSet into OneWithMany.
To ensure I don't miss properties added in future I am using ProjectTo in AutoMapper - but I can't work out how to fit it into the equation.
Unfortunately, it seems Entity Framework does not support GroupJoin.
The solution is to do the projection and as much filtering as possible as two separate queries, and then combine them into a result in memory.
If you find EF related answers on the web related to GroupJoin make sure you check the example code to see if they are actually showing code working on arrays instead of DbSet.
I am using EF6, WebApi2, AngularJS and BreezeJs.
I have the following entities:
Person
{
public string Name { get; set;}
public virtual ICollection<GenericProfileCountry> Countries { get; protected set; }
}
public class GenericProfileCountry
{
public string PersonId{ get; set; }
public virtual Person Person{ get; set; }
public string CountryIso { get; set; }
public virtual Country Country { get; set; }
}
public class Country
{
public string Iso { get; set; }
public string Name { get; set; }
}
Now I have a query that brings all Persons through breeze as follows:
return zEntityQuery.from('Contacts').expand('Profile, Countries')
.orderBy(contactOrderBy)
.toType(entityName)
.using(self.manager).execute()
.to$q(querySucceeded, self._queryFailed);
What I would like to do is perform a where statement on the above query with criteria that are on the intermediate entity. So say I want to bring only contacts that their first country (a person can have multiple countries) iso code is equal to 'GB'.
In Linq it would be something like Contacts.Where(contact => contact.Countries.First().CountryIso == 'GB')
Could something similar be expressed in the where(predicate) of breeze? I thought of going the other way (start from the intermediate table and filter from there), but not sure if that is the correct approach.
You can achieve that by creating a predicate with the keyword any or all
.where('Countries','any','CountryIso','eq','GB')
In case you want to create a predicate on grand children : BreezeJS Predicates on 2nd level expanded entities
Edit
If you want to get the first contacts whose countries Isos start with 'GB', you can achieve that by:
Jay's suggestion.
using Linq at Breeze controller:
public IQueryable<Person> ContactsWithFilteredCountryIso(string CountryIso)
{
return _contextProvider.Context.Persons.Where(p => p.Countries.First().CountryIso== CountryIso);
}
Then on the client:
return zEntityQuery.from('Contacts')
.withParameters({ CountryIso: "GB"})
.expand('Profile, Countries')
.orderBy(contactOrderBy)
.toType(entityName)
.using(self.manager).execute()
.to$q(querySucceeded, self._queryFailed);
Writing a select projection on countries with bringing up Contacts can be implemented by issuing a Breeze query on countries and expanding contact:
return zEntityQuery.from('Countries').expand('Contact')
.select('Country.name')
.where('CountryIso','eq','GB')
.orderBy(contactOrderBy)
.toType(entityName)
.using(self.manager).execute()
.to$q(querySucceeded, self._queryFailed);
Given the following:
public class Department
{
public int DepartmentID { get; set; }
public string Name { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
public class Course
{
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public int DepartmentID { get; set; }
public virtual Department Department { get; set; }
}
If I turn lazy loading off and issue the following:
var departments = _DepartmentRepository.GetAll()
.Include(c => c.courses);
Then I get the answers with a Department object inside of them.
Is there a way I can just include the courses and not get back the Department object. For example can I just include one level (courses).
You are just including one level. The department object inside the course is there because EF has done some relationship fixup so that you can navigate to the department from the course.
If you don't want departments then just get the courses directly. That is context.Courses.ToList(); or via a courses repo if you have one.
When fetching entities EF will automatically populate navigation properties where it is already tracking the target object. This means if you do say:
// Load the department with a PK of 1
_DepartmentRepository.Find(1);
and then, using the same context, for example:
// Load a course with PK of 17
_CourseRepository.Find(17);
If this courses department id is 1, then EF will have automatically populated it's Department navigation property even though you didn't specify the include. You could stop this behavior by not making the Department navigation property virtual.
Sorry about the title; couldn't think of a better one.
Any way, I'm accessing an associated property in my view like so:
#Model.Company.CompanyName // No problems here...
The model is a viewmodel mapped to an EF POCO. The Model has several properties associated to the Company table. Only one of the properties in the model share the same name as the PK in the Company table. All the other properties reference the same table:
public class MyModelClass
{
public int Id { get; set; }
public int CompanyId { get; set; }
public int AnotherCompanyId { get; set; } // References CompanyId
public int AndAnotherCompanyId { get; set; } // References CompanyId
public Company Company { get; set; }
}
public class Company
{
public int CompanyId { get; set; }
public string CompanyName { get; set; }
public string Address { get; set; }
}
I'm obviously missing something here.
How can I get the names of the other companies in my Model?
Any help is greatly appreciated.
The model is a viewmodel mapped to an EF POCO
I think you are confusing the notion of a view model. A view model is a class that is specifically designed to meet the requirements of your view. So if in your view you need to display the company name and not the company id then your view model should directly contain a CompanyName property. Or a reference to another view model (CompanyViewModel) which contains the name directly. It is then the responsibility of your controller action to query your domain models (EF entities) and aggregate them into a single view model tat will contain all the necessary information that the view requires.
Here's how a typical view model might look like:
public class MyViewModel
{
public CompanyViewModel Company { get; set; }
public CompanyViewModel AnotherCompany { get; set; }
public CompanyViewModel AndAnotherCompany { get; set; }
}
public class CompanyViewModel
{
public string Name { get; set; }
}
Where the data comes from in this view model is not important. You could have the Company property populated from your EF stuff, the AnotherCompany property populated from a XML file and AndAnotherCompany from WCF.
Setup
Using MVC 3 + Code First
Here are my classes
public class Member
{
[Key]
public Guid ID { get; set; }
[Required]
public String Email { get; set; }
[Required]
public String FirstName { get; set; }
[Required]
public String LastName { get; set; }
public String Sex { get; set; }
public String Password { get; set; }
public String PasswordSalt { get; set; }
public DateTime RegisterDate { get; set; }
public DateTime LastOnline { get; set; }
public String SecurityQuestion { get; set; }
public String SecurityAnswer { get; set; }
public virtual ICollection<FamilyMember> Families { get; set; }
public virtual ICollection<Relationship> Relationships { get; set; }
}
public class Relationship
{
[Key]
public Guid ID { get; set; }
[ForeignKey("Member1")]
public Guid Member1ID { get; set; }
[ForeignKey("Member2")]
public Guid Member2ID { get; set; }
public Guid RelationshipTypeID { get; set; }
public virtual RelationshipType RelationshipType { get; set; }
public virtual Member Member1 { get; set; }
public virtual Member Member2 { get; set; }
}
Here is the problem
The database table "Relationship" is being created with the following columns:
ID, Member1ID, Member2ID, RelationshipTypeID, Member_ID
Why is it creating the Member_ID column?
I've seen this post in which the user has the same type of setup, but I am unsure of how to define the InverseProperty correctly. I tried using fluent API calls but from what I can tell they will not work here since I have two foreign keys referring to the same table.
Any help would be appreciated!
Member_ID is the foreign key column which EF created for the navigation property Member.Relationships. It belongs to a third association from Member.Relationships refering to an end endpoint which is not exposed in your Relationship entity. This relationship has nothing to do with the other two relationships from Relationship.Member1 and Relationship.Member2 which also both have an endpoint not exposed in Member.
I guess, this is not what you want. You need always pairs of endpoints in two entities to create an association. One endpoint is always a navigation property. The second endpoint can also be a navigation property but it is not required, you can omit the second navigation property.
Now, what is not possible, is to associate two navigation properties (Member1 and Member2) in one entity with one navigation property (Relationships) in the other entity. That is what you are trying to do apparently.
I assume that your Member.Relationships property is supposed to express that the member is either Member1 or Member2 in the relationship, or that it participates in the relationship, no matter if as Member1 or Member2.
Unfortunately you cannot express this in the model appropriately. You have to introduce something like RelationsshipsAsMember1 and RelationsshipsAsMember2 and for these two collection you can use the InverseProperty attribute as shown in the other question. In addition you can add a helper property which concats the two collections. But this is not a mapped property but readonly:
public class Member
{
// ...
[InverseProperty("Member1")]
public virtual ICollection<Relationship> RelationshipsAsMember1 { get; set; }
[InverseProperty("Member2")]
public virtual ICollection<Relationship> RelationshipsAsMember2 { get; set; }
public IEnumerable<Relationship> AllRelationships
{
get { return RelationshipsAsMember1.Concat(RelationshipsAsMember2); }
}
}
Accessing AllRelationships will cause two queries and roundtrips to the database (with lazy loading) to load both collections first before they get concatenated in memory.
With this mapping the Member_ID column will disappear and you will only get the two expected foreign key columns Member1ID, Member2ID because now you have only two associations and not three anymore.
You could also think about if you need the Relationships collection in the Member entity at all. As said, navigation properties on both sides are not required. If you rarely need to navigate from a member to its relationships you could fetch the relationships also with queries on the Relationship set, like so:
var relationships = context.Relationships
.Where(r => r.Member1ID == givenMemberID || r.Member2ID == givenMemberID)
.ToList();
...or...
var relationships = context.Relationships
.Where(r => r.Member1ID == givenMemberID)
.Concat(context.Relationships
.Where(r => r.Member2ID == givenMemberID)
.ToList();
This would give you all relationships the member with ID = givenMemberID participates in without the need of a navigation collection on the Member entity.