Accessing foreign key from a one to many relationship using LINQ - linq

I'm using MVC4 with Entity Framework code first.
I have the following models:
public class Member {
public int ID { get; set; }
[Display(Name = "First Name")]
[Required(ErrorMessage = "Please enter a first name.")]
public string FirstName { get; set; }
[Display(Name = "Last Initial")]
[Required(ErrorMessage = "Please enter the last initial of your last name.")]
[MaxLength(1)]
public string LastName { get; set; }
[Display(Name = "City")]
[Required(ErrorMessage = "Please enter your city.")]
public string City { get; set; }
public virtual ICollection<Favorite> Favorites { get; set; }
}
public class Favorite {
public int ID { get; set; }
public String Type { get; set; }
public String Value { get; set; }
}
My code is receiving a list of search terms List<string> searchTerms from the front end. I need to search the values of every members' favorites for every search term.
I'm working the following LINQ statement:
return db.Favorites.Where(f => searchTerms.Any(s => f.Value.Contains(s))).ToList();
My issue is this will return a List<Favorite> which is great but what I really need is the ID of the member associated in the database. This unfortunately is not an option I can pick in LINQ
return db.Favorites.Where(f => searchTerms.Any(s => f.Value.Contains(s))).Select(f => f.????) .ToList();
The ??? only gives me ID, Type, Value. The properties of Favortie, however in the database table Entity framework created there's a foreign key column Member_ID. But I can't pick that in C#.
So my questions is... how do I? Do I just add a public virtual int MemberID in the Favorite class? Will entity framework automatically associate that to the foreign key?

Based on Code First conventions, for adding the foreign key just:
public int MemberID { get; set; }
If you need the navigation property then
public virtual Member Member { get; set; }
Check this MSDN page for further information

You can get the a collection of Members with a filtered children collection.
Example:
return db.Members.Select(member => new
{
Member = member,
Favorites = member.Favorites.Where(f => searchTerms.Any(s => f.Value.Contains(s)))
}).Where(m => m.Favorites.Any()).ToList();
Don't forget the IEqualityComparer, or convert to uppercase or lowercase both the search terms and the value

This works:
return db.Members.Select(member => new
{
Member = member,
Favorites = member.Favorites.Where(f => searchTerms.Any(s => f.Value.Contains(s)))
}).Where(m => m.Favorites.Any()).ToList();

Related

Configuring intersection table in EF Core 2.1.1

I have a pretty simple need, but I can't figure out how to do it with EF core 2.1.1 in code first.
I have a table Right and a table Role:
Role
public int RoleId { get; set; }
public string Name { get; set; }
Right
public int RightId { get; set; }
public string Name { get; set; }
Usually, in a standard database, I would simply make an intersection table Named:
RoleRights(RoleId int, RightId int)
But it seems in ef core 2.1.1, you instead add navigation properties.
Role
public int RoleId { get; set; }
public string Name { get; set; }
public IEnumerable<Right> Rights { get; set; }
Right
public int RightId { get; set; }
public string Name { get; set; }
public IEnumerable<Role> Roles { get; set; }
A Role can contain any number of Right and a Right can be contained in any number of Role.
By doing:
modelBuilder.Entity<Role>().HasMany(r => r.Rights);
modelBuilder.Entity<Right>().HasMany(r => r.Roles);
It flattens my Role table and add a RightId instead of making an intersection table. Same thing for the Right table. It adds a RoleId.
In the Migration script:
migrationBuilder.AddColumn<int>(
name: "RightId",
table: "Roles",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "RoleId",
table: "Rights",
nullable: true);
migrationBuilder.AddForeignKey(
name: "FK_Rights_Roles_RoleId",
table: "Rights",
column: "RoleId",
principalTable: "Roles",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
migrationBuilder.AddForeignKey(
name: "FK_Roles_Rights_RightId",
table: "Roles",
column: "RightId",
principalTable: "Rights",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
How can I configure my model to have an intersection table instead? In this case, it is generating a wrong schema. I cannot insert and empty Role or a Right in no Role. Thinking of it, I should probably never do that anyway, but it feels wierd to me.
Thanks for your time!
If anything is not clear, tell me what needs more detail and I'll clarify!
So I had followed something outdated. The solution is to explicitly make the join table.
public class RoleRight : IEntity
{
public int RoleId { get; set; }
public Role Role { get; set; }
public int RightId { get; set; }
public Right Right { get; set; }
}
With both Right and Role looking like this.
public class Right : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<RoleRight> RoleRights { get; set; }
}
With this configuration on the OnModelCreating
modelBuilder.Entity<RoleRight>().HasKey(rr=> new { rr.RightId, rr.RoleId });
modelBuilder.Entity<RoleRight>().HasOne(rr => rr.Right)
.WithMany(r => r.RoleRights)
.HasForeignKey(rr => rr.RightId);
modelBuilder.Entity<RoleRight>().HasOne(rr => rr.Role)
.WithMany(r => r.RoleRights)
.HasForeignKey(rr => rr.RoleId);
Which is basically the last section in the link I provided in the comment earlier.
I have no clue how I had missed it when I read the page the first time!

Unable to create a constant value of type ''YYY". Only primitive types or enumeration types are supported in this context

I have an entity Person and a view model that contains a collection of persons.
public class ViewModel
{
public string Name { get; set; }
public string Description { get; set; }
public int Project { get; set; }
public ICollection<PersonsViewModel> PersonCollection { get; set; }
}
public class PersonsViewModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
These are my tries:
1e:
ICollection<Person> prsn = new PersonRepository().GetAll().Where(x => vm.PersonCollection.Select(y => y.Id).Contains(x.Id)).ToList();
2e:
ICollection<Person> prsn = (from st in new PersonRepository().GetAll()
from qw in cm.PersonCollection
where st.Id == qw.Id
select st).ToList();
Based on this blog post, 3e:
ICollection<Person> prsn = (from st in new PersonRepository().GetAll()
from qw in cm.PersonCollection
where st.Id.Equals(qw.Id)
select st).ToList();
What i'm trying to do is, select the person entity from the datacontext, based on the person id's from the view model. In all the 3 (i did more tries but i lost count) cases i ended up with the run time error as described in the title.
I also found the same question asked here on SO, but it was kind of hard to compare it with mine as there was no extra code available like model/entity.
Can someone point me into the right direction?
You can do like this ,
var Ids=vm.PersonCollection.Select(y => y.Id).ToArray();
ICollection<Person> prsn =
new PersonRepository().GetAll().Where(x => Ids.Contains(x.Id)).ToList();

Is there a way to search two different collection and fill another collection using LINQ?

I have two different collections like below
public class Student
{
public int StudentID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Skills
{
public int SkillID { get; set; }
public int StudentID { get; set; }
public string Keyskill_Name { get; set; }
public int LastUsedYear { get; set; }
}
Here one student can contain multiple keyskils
I just want to fill a new collection like below
public class StudentDetails
{
public int StudentID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public list<string> Keyskill_Name { get; set; }
}
Please help me. Thanks in advance.
The GroupJoin LINQ method is the perfect solution for this case:
List<Student> students = new List<Student>();
List<Skills> skills = new List<Skills>();
List<StudentDetails> studentDetails = students.GroupJoin(skills, student => student.StudentID, skill => skill.StudentID, (student, skillsForStudent) => new StudentDetails
{
FirstName = student.FirstName,
LastName = student.LastName,
StudentID = student.StudentID,
Keyskill_Name = (from skill in skillsForStudent
select skill.Keyskill_Name).ToList()
}).ToList();
How to use the GroupJoin method?
Call the method from the set that gives a 1 to 1 relation with the result (Here for each Student, there is one and only one corresponding StudentDetails.
The first argument of the method is the set you want to "distribute" on the other items (Here the skills are "distributed" over the entire set of students)
The second and third arguments are used to explain how to make the collision between the elements of the first set and the elements of the second set. Lambda expressions are extremely useful in this case.
Lastly, the fourth argument, is used to define the result. It is a function with 2 parameters : an element of the first set (here students) and its corresponding elements in the second set that have been found using the collision.

EF 4.1 in MVC3 using Lazy Loading

EF 4.1 in MVC3 and Lazy loading, using code first model
I am using Membership API for creating an account. Once the account is created successfully. I redirect to create a Contact record automatically.
contactId (auto database generated), userid (storing the user id that was generated by membership api)
The models are:
public class Contact
{
public int ContactID { set; get; }
public string UserId { set; get; }
public string LastName { set; get; }
public int? CompanyID { set; get; } // not sure if I need this as it will be NULL
public virtual Company CompanyInfo { set; get; }
}
next the user can click on create Company link or logout & login later to create the company record.
public class Company
{
public int CompanyID { set; get; }
public int ContactID { set; get; }
public string CompanyName { set; get; }
public virtual Contact Contacts { set; get; }
}
When the user decides to create company record, I am checking if company already exists, if exists I am just showing the contact information and the company information OR if not found I redirect to create company.
public ActionResult chckifCompanyFound()
{
int contactId = 1; //Assuming I retrieved the value
//I think I should get the data from Company table, if company data found then contact data could be retrieved using lazy loading?
Company c= db.Company.Include(c => c.Contacts).Where(x => x.ContactID == contactId).FirstOrDefault();
if(c == null)
//redirect to create company
else
// view data from c company object
}
currently it shows an exception once it tries to create contact record after membership API creates an account. I create the record like this:
Contact contact = new Contact();
contact.UserId = userId;
contact.LastName = lastName;
db.Contacts.Add(contact);
db.SaveChanges();
Exception:
Unable to determine the principal end of an association between the types 'D.Models.Contact' and 'D.Models.Company'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
thank you so much!
Try the following (from this link):
Contact cc = db.Contacts.Include( "CompanyInfo" ).Where(x => x.ContactID == product.ContactID).FirstOrDefault();
Try replacing your models with these:
public class Contact
{
public int ContactId { set; get; }
public string UserId { set; get; }
public int CompanyId { get; set; }
public string LastName { set; get; }
public virtual Company Company { set; get; }
}
public class Company
{
public int CompanyId { set; get; }
public string CompanyName { set; get; }
public virtual ICollection<Contact> Contacts { set; get; }
}
Your Contact needs a CompanyId since it has only a single company related to it, and it will act as a foreign key between that contact and the company. The navigation property CompanyInfo will be used for the lazy loading. Your Company object only needs the Contacts collection because the Contact is where the relationship is created in the database.
To answer your question about the query I need more information... where does the product come in to play? I don't see it referenced from the contact or company, but if you want to get the Company of a Contact, simply do this:
var company = dbContext.Contacts.Find(userId).Company;
Console.WriteLine("Company Name: {0}", company.CompanyName);
try:
Contact cc = db.Contacts.Include(c=>c.CompanyInfo).Where(x => x.ContactID == product.ContactID).FirstOrDefault();

EF 4.1 Code First Relationship table

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.

Resources