EF Code First foreign key definition / left join / navigation properties? - c#

Let's say I have 3 tables:
[Table("Comments")]
public class Comment {
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[ForeignKey("Users")]
public int UserId { get; set; }
public virtual Users Users { get; set; }
public string Text { get; set; }
}
[Table("Users")]
public class Users {
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string UserName { get; set; }
}
[Table("CommentAgree")]
public class CommentAgree {
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int CommentId { get; set; }
public int UserId { get; set; }
}
Users post comments and other users can 'agree' with that comment, a bit like Facebook's 'like' system. I'm using lambda for my queries and I can do:
var query = db.Comments.Select(c => new {
c.Id,
c.Users.UserName,
c.Text
});
How can I create a join to CommentAgree on Comment.Id = CommentAgree.CommentId? I could write the join in Lambda but I need it to be a left join as nobody may agree with the comment but I still want it to display.
I want to do it the right way, so I'm open to suggestions whether to do it by foreign keys, lambda joins, navigation properties... or something else?
Is this possible?
Thanks

The best approach is probably to use the features of Entity Framework and create navigation properties rather than explicitly using LINQ to perform the joins just for related data.
If your types are shaped just for the purposes of data access, then adding navigation properties to both ends of the relationship is probably a good idea, along with the foreign key properties that you already have.
The collection navigation property on Comment should implement ICollection (for example List<CommentAgree>), and you would have a reference navigation property of type Comment on the CommentAgree type.
You would then have to define the relationships in your mappings, either using data annotations or (preferably) the fluent API.
To load the related data, you could either use lazy loading, or eager loading (using the Include extension method), or use explicit loading from the entry information for the entity.

Related

Configure One-None/One Relationship with Multiple Tables using Entity

I'm in a situation where one table has two One-None/One Relationships. How do I implement this using Entity Framework Code-First?
I've seen the following links
https://www.safaribooksonline.com/library/view/programming-entity-framework/9781449317867/ch04s07.html
https://cpratt.co/0-1-to-1-relationships-in-entity-framework/
https://www.tektutorialshub.com/one-to-one-relationship-entity-framework/
Where essentially it's said that the dependent end needs to have a primary key that is the same as that of the principal end. But I'm weary of implementing this with more than one One-None/One Relationship without confirmation and proper knowledge of what's going on. Furthermore I am not sure how to construct statements as it does not have a conventional Foreign Key.
I've also seen Configuring multiple 1 to 0..1 relationships between tables entity framework which confused me beyond recognition.
See below for the relevant part of my DB Diagram:
So Essentially, a Player shouldn't be saved without a DKImage, similarly a Product shouldn't be saved without a DKImage.
Below is the code for Models: Players, Products, DKImages (I know it's not correct, I only implemented it this way so I can generate the database and show the diagram)
Player
public enum Positions { PG, SG, SF, PF, C }
public class Player
{
[Key]
[ForeignKey("Images")]
public int PlayerID { get; set; }
[Required]
public string PlayerName { get; set; }
[Required]
public string PlayerLastName { get; set; }
[Required]
public int PlayerAge { get; set; }
[Required]
public Positions Position { get; set; }
[Required]
public bool Starter { get; set; }
[Required]
[Display(Name = "Active / Not Active")]
public bool Status { get; set; }
//Foreign Keys
public int PlayerStatsID { get; set; }
//Navigation Properties
[ForeignKey("PlayerStatsID")]
public virtual IQueryable<PlayerStats> PlayerStats { get; set; }
public virtual DKImages Images { get; set; }
}
DKImages
public class DKImages
{
[Key]
public int ImageID { get; set; }
[Required]
public string ImageURL { get; set; }
[Required]
public DateTime DateUploaded { get; set; }
//Foreign Keys
[Required]
public int CategoryID { get; set; }
//Navigation Properties
public virtual Products Products { get; set; }
public virtual Category Category { get; set; }
public virtual Player Player { get; set; }
}
Products
public class Products
{
[ForeignKey("Images")]
[Key]
public int ProductID { get; set; }
[Required]
public string ProductName { get; set; }
[Required]
public DateTime DateAdded { get; set; }
//Foreign Keys
[Required]
public int ProductTypeID { get; set; }
//Navigation Properties
[ForeignKey("ProductTypeID")]
public virtual ProductType ProductType { get; set; }
public virtual DKImages Images { get; set; }
}
Edit
I have been told that the code above is correct. If so then how do I create CRUD LINQ Statements (Or any method of constructing CRUD statements for that matter) with the above code.
What you want here is referred to as polymorphic associations: several entities having child entities of one type. They're typically used for comments, remarks, files etc. and usually applied to 1:n associations. In your case there are polymorphic 1:1 associations. Basically these associations look like this (using a bit more generic names):
How to implement them?
Entity Framework 6
In EF6 that's problem. EF6 implements 1:1 associations as shared primary keys: the child's primary key is also a foreign key to its parent's primary key. That would mean that there should be two FKs on Image.ID , one pointing to Person.ID and another one pointing to Product.ID. Technically that's not a problem, semantically it is. Two parent entities now own the same image or, stated differently, an image should always belong to two different parents. In real life, that's nonsense.
The solution could be to reverse the references:
But now there's another problem. The entity that's referred to is named the principal, the other entity is dependent. In the second diagram, Image is the principal, so in order to create a Person, its image must be inserted first and then the person copies its primary key. That's counter-intuitive and most likely also impractical. It's impossible if images are optional.
Nevertheless, since in your case you want images to be required let me show how this association is mapped in EF6.
Let's take this simple model:
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public virtual Image Image { get; set; }
}
public class Product
{
public int ID { get; set; }
public string Name { get; set; }
public virtual Image Image { get; set; }
}
public class Image
{
public int ImgID { get; set; } // Named for distinction
public string Url { get; set; }
}
The required mapping is:
modelBuilder.Entity<Image>().HasKey(pd => pd.ImgID);
modelBuilder.Entity<Person>().HasRequired(p => p.Image).WithRequiredDependent();
modelBuilder.Entity<Product>().HasRequired(p => p.Image).WithRequiredDependent();
As you see, Image has two required dependents. Perhaps that's better than two required parents, but it's still weird. Fortunately, in reality it's not a problem, because EF doesn't validate these associations. You can even insert an image without a "required" dependent. I don't know why EF doesn't validate this, but here it comes in handy. The part WithRequiredDependent might as well have been WithOptional, it doesn't make a difference for the generated data model, but at least this mapping conveys your intentions.
An alternative approach could be inheritance. If Person and Product inherit from one base class this base class could be the principal in a 1:1 association with Image. However, I think this is abusing a design pattern. People and products have nothing in common. From a design perspective there's no reason for them to be part of one inheritance tree.
Therefore, in EF6 I think the most feasible solution is to use the third alternative: separate image tables per entity.
Entity Framework Core
In EF-core 1:1 associations can be implemented the EF6 way, but it's also possible to use a separate foreign key field in the dependent entity. Doing so, the polymorphic case looks like this:
The Image class is different:
public class Image
{
public Image()
{ }
public int ImgID { get; set; }
public int? PersonID { get; set; }
public int? ProductID { get; set; }
public string Url { get; set; }
}
And the mapping:
modelBuilder.Entity<Person>().Property(p => p.ID).UseSqlServerIdentityColumn();
modelBuilder.Entity<Person>()
.HasOne(p => p.Image)
.WithOne()
.HasForeignKey<Image>(p => p.PersonID);
modelBuilder.Entity<Product>().Property(p => p.ID).UseSqlServerIdentityColumn();
modelBuilder.Entity<Product>()
.HasOne(p => p.Image)
.WithOne()
.HasForeignKey<Image>(p => p.ProductID);
modelBuilder.Entity<Image>().HasKey(p => p.ImgID);
Watch the nullable foreign keys. They're necessary because an image belongs to either a Person or a Product. That's one drawback of this design. Another is that you need a new foreign key field for each new entity you want to own images. Normally you want to avoid such sparse columns. There's also an advantage as compared to the EF6 implementation: this model allows bidirectional navigation. Image may be extended with Person and Product navigation properties.
EF does a pretty good job translating this into a database design. Each foreign key has a filtered unique index, for example for Person:
CREATE UNIQUE NONCLUSTERED INDEX [IX_Image_PersonID] ON [dbo].[Image]
(
[PersonID] ASC
)
WHERE ([PersonID] IS NOT NULL)
This turns the association into a genuine 1:1 association on the database side. Without the unique index it would be a 1:n association from the database's perspective.
An exemple in your Player table would be this :
public class Player
{
// All the rest you already coded
[Required]
public int ImageID
[ForeignKey("ImageID")]
public virtual DKImage DKImage {get;set;}
}
This would force a player to have a DKImage, but as said in the comments, this create a one to many relationship.
Another way out would be to put all Player fields into the DKImage table, those fields would be null if there is no player associated to this DKImage.
Edit for 1 to 1..0
Ivan Stoev's link got some pretty interesting insight on how to accomplish this :
https://weblogs.asp.net/manavi/associations-in-ef-4-1-code-first-part-3-shared-primary-key-associations
It seems like you will have to put a bit more code in your class :
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<DKImage>().HasOptional(t => t.Player).WithRequired();
}
If the tutorial is correct, this would read as :
"DKImage entity has an optional association with one Player object but this association is required for Player entity".
I have not tested it yet.

Trouble querying a collection of a collection

I'm having issues querying for a specific property using linq in EF.
To outline, Users have associated roles. Each Role has Associated groups. I'm just trying to get the MAMUserGroup property of all the groups associate with the user. I can easily get the associate Roles with .Include(), but am having trouble going the one extra level down to the associated MAMUserGroups.
User model:
public class User
{
[Display(Name = "SSO")]
[Required]
[StringLength(9, ErrorMessage = "SSO must be 9 numbers", MinimumLength = 9)]
public virtual string ID { get; set; }
[Display(Name = "First Name")]
[Required]
public string FirstName { get; set; }
[Display(Name = "Last Name")]
[Required]
public string LastName { get; set; }
[Display(Name = "MAM Roles")]
public ICollection<MAMRoleModel> MAMRoles { get; set; }
}
MAMRole Model:
public class MAMRoleModel
{
public int ID { get; set; }
public string Name { get; set; }
public ICollection<MAMUserGroupModels> MAMUserGroups { get; set; }
}
MAM Group Model:
public class MAMUserGroupModels
{
public int ID { get; set; }
public string MAMUserGroup { get; set; }
}
I've tried
foreach(var bar in user.MAMRoles)
{
foreach(var foo in bar.MAMUserGroups)
{
//do something
}
}
But got null reference error. I've also just tried from haim770
var test = db.Users.Include(x => x.MAMRoles.Select(y=> y.MAMUserGroups));
but MAMUserGroup is a count of 0, so it's not seeing the reference.
I think the problem arises because you didn't declare your ICollection in the one-to-many relations in entity-framework as virtual. Declaring the ICollection virtual would solve it
To prevent future problems, consider to make your classes more entity-framework compliant. This diminishes the use of all kinds of attributes. Proper use of plurals and singulars improves readability of the queries which helps those who have to change the code in future.
The primary key 'ID' should not be virtual
In your one-to-many use the virtual ICollection on the one-side, and add add a reference to the one and the foreign key on your many-side
Consider using standard naming conventions. This helps entity-framework to define a model without you having to help it using all kinds of attributs.
Only deviate from the standard naming conventions if you really have to. In that case add Attributes fro primary key, foreign key, one-to-many relation etc, or consider using fluent API.
.
public class User
{
// Standard naming convention: automatic primary key
public string ID { get; set; }
// a user has many MAMRoles.
// standard naming convention: automatic one-to-many with proper foreign key
// declare the collection virtual!
public virtual ICollection<MAMRole> MAMRoles { get; set; }
}
public class MAMRole
{
// standard naming cause automatic primary key
public int ID { get; set; }
// a MAMRole belongs to one user (will automatically create foreign key)
// standard naming cause proper one-to-many with correct foreign key
public string UserId {get; set;}
public virtual User User {get; set;}
// A MamRole has many MAMUserGroupModels
// same one-to-many:
// again: don't forget to declare the collection virtual
public virtual ICollection<MAMUserGroupModel> MamUserGroupModels{ get; set; }
}
public class MAMUserGroupModel
{
// automatic primary key
public int ID {get; set;}
// a MAMUserGroupModel belongs to one MAMUser
// automatic foreign key
public int MAMUserId {get; set;}
public virtual MAMUser MAMUser {get; set;}
}
By the way. Entity framework knows it needs to get the value of a property as soon as you use it in your IQueryable. Only if you want to select property values and get them to local memory you'll need to use Include. This makes the query more efficient as only the used values are selected.
So if you only want to do something with the MamUserGroup inside your MamUserGroupModel, don't include anything:
In baby steps:
IQueryable<NamRole> mamRolesOfAllUsers = myDbContext.Users
.SelectMany(user => user.MamRoles);
IQueryable<MamRoleModel> mamUserGroupModelsOfAllUsers = mamRolesOfAllUsers
.SelectMany(mamRole => mamRole.MamUserGroupModels);
IQueryable<string> mamUserGroups = mamUserGroupModelsOfAllUsers
.Select(mamUserGroupModel => mamUserGroupModeModel.MamUserGroup;
Or in one statement
IQueryable<string> mamUserGroups = myDbContext.Users
.SelectMany(user => user.MamRoles)
.SelectMany(mamRole => mamRole.MamUserGroupModels)
.Select(mamUserGroupModel => mamUserGroupModel.MamUserGroup);
Note that until know I haven't communicated with the database yet. I've only created the expression of the query. The query will be done once the enumeration starts:
foreach(var userGroup in mamUserGroups)
{
...
}
Note the use of SelectMany instead of Select. Whenever you have collections of collections, use SelectMany to make it one collection. If you see yourself making a foreach within a foreach, this is a good indication that a SelectMany probably would have been a better choice.
Finally: did you see that because of proper use of plurals and singulars the queries are much more readable. Less changes of making mistakes when changes have to be implemented by someone else in future.

How to ask Automapper to grab related record by inner join on Id field which is not a foreign key?

I’ve been using Automapper for a while now, and so far it all works great. But recently I came across some “limitation” (or lack of my knowledge).
Let me give you a simplified example with two classes:
public class Consumable
{
public int ConsumableId { get; set; }
public string Description { get; set; }
public int SaleDepartmentId { get; set; }
}
public class SaleDepartment
{
public int SaleDepartmentId { get; set; }
public string Description { get; set; }
}
These two entities store the Id of SaleDepartment, but there is not foreign key linking SaleDepartment to Consumable (and I don’t want it as a key), however SaleDepartment has PrimaryKey on SaleDepartmentId
Now my DTO looks very similar
public class ConsumableDTO
{
public int ConsumableId { get; set; }
public string Description { get; set; }
public int SaleDepartmentId { get; set; }
}
Here is the mapping
Mapper.CreateMap<Consumable, ConsumableDTO>().ReverseMap();
So anytime I bring a Collection of ConsumableDTO’s I also want to bring the related SaleDepartment’s descriptions,
If there was a navigation property I would do something like this
Mapper.Map<ObservableCollection<Consumable>>
(context.Consumable.Project().To<ConsumableDTO>());
But because such a key does not exist, how would I tell the automapper to do inner join based on these Ids I have?
I have spent two days and I found a way of doing it, but I am not convinced that this the right way, and I am wondering whether I am missing a trick here and there is an easier or better way of achieving this with the automapper.
This is how I achieved getting the related record
var foo = new ObservableCollection<Consumable>(
(from c in context.Consumable.Project().To<ConsumableDTO>()
join sd in context.SaleDepartment on c.SaleDepartmentId equals sd.SaleDepartmentId
select new
{
consumable = c,
SaleDepartmentDescription = sd.Description
}).ToList()
.Select(p => Mapper.Map<ConsumableDTO, Consumable>(p.consumable, new Consumable()
{
SaleDepartmentDescription = p.SaleDepartmentDescription
})));
So, this will grab or consumable and then inner join saledeparments and select description form that inner join, but it seems like quite few steps, is there an easier way of telling the automapper, grab that related record based on this matching Id?
Thank you for your attention and time.
First, I'm assuming your DTO is meant to contain public string SaleDepartmentDescription { get; set; } as your question refers to it but it isn't actually there.
If you are NOT using EF migrations (a fair assumption since otherwise you'd just add the foreign key!), then you can do this by adding keys in your Entities - the keys don't actually need to present in the database for EF to join on them, this just tells EF to pretend that they are. (If you are using EF migrations then this approach will not work, as it will want to add the keys to the DB.)
public class Consumable
{
public int ConsumableId { get; set; }
public string Description { get; set; }
public int SaleDepartmentId { get; set; }
[ForeignKey("SaleDepartmentId")]
public virtual SaleDepartment SaleDepartment { get; set; }
}
Assuming your DTO does contain the string property SaleDepartmentDescription then AutoMapper will handle this automatically, though you should use ProjectTo to make more efficient database queries:
var mappedDTOs = context.Consumable.ProjectTo<ConsumableDTO>().ToList();

One to many config in Entity Framework and Join Expressions

I am using EF codefirst .I am confused on relationship in Entities .I have Two entities Student and Standard . Shown below
public class Student
{
public int StudentId { get; set; }
public string StudentName { get; set; }
public int StdandardId { get; set; }
}
public class Standard
{
public int StandardId { get; set; }
public string StandardName { get; set; }
public string Description { get; set; }
}
They have One to Many relationship.
I can do this by simple join expression like this
var list = StudentList.Join
(StandardList,c => c.StdandardId,o => o.StandardId,(c, o) => new
{
StudentId = c.StudentId,
StudentName = c.StudentName,
StandardName = o.StandardName
});
Then why should i configure forienkey One-to-Many relationship like
public class Student
{
public Student() { }
public int StudentId { get; set; }
public string StudentName { get; set; }
public int StdandardId { get; set; }
public virtual Standard Standard { get; set; }
}
public class Standard
{
public Standard()
{
Students = new List<Student>();
}
public int StandardId { get; set; }
public string StandardName { get; set; }
public string Description { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
Is there any key benifits.? Which one will perform well ?
The navigation property (Students) is an implicit join. but it's an outer join. If you join explicitly you can enforce an inner join, which will generally perform better. So do this if performance is critical.
So give yourself the opportunity to do both. Create navigation properties and join explicitly when necessary.
The benefit of navigation properties is a much more succinct syntax. e.g.
from standard in Standards
select new { standard.StandardName , NrOfStudents = standard.Students.Count() })
For this query you always want an outer join, because you'd also want to report the standards with zero students.
Or an implicit SelectMany:
from standard in Standards
where standard.StandardId == id
from student in standard.Students
select new { student. ... }
Navigation properties help you to carry out joins without this verbose join syntax.
This question can result in answers ranging to the size of essays.
I'll try to keep it essential. In short, Foreign Keys are used to ensure referential integrity.
In your select statement it might not make a difference, but think about update, insert and delete statements, and hurdles you'd have to take in order to cascade everything down to the last table.
Lets assume your Foreign Key constraint is set to Cascade. Whenever you make a change in your mastertable, the change is cascaded down to every child table. You'd have to manually join every table in your statement to implement the same.
If the constraint is set to Restrict you can not delete a Student as long as there are still Standards referencing to it. Again, You'd have to check this by hand in your statements every time.
You may very well be able to do this in your head, but you will make an error, and then you may be stuck with inconsistent data, when money is on the line.
And then there is comfort in EF.
I could join my data, but if there are Foreign Keys and tehrefore relationships in place I could write
var students = context.Students.Include(o => o.Enrollments);
and use it in my View with
#foreach(var enrollment in Model.Enrollments)
{
//...
}
In essence, this is not a problem related purely to Entity Framework, but to relational databases.
Have a look at ACID and Foreign Keys

How to avoid Lazy Loading when accessing foreign key ID property through navigation property?

I'm in the process of converting a project from NHibernate to Entity Framework 6.
Given this simple model:
public class User
{
public int ID { get; set; }
public string FullName { get; set; }
public virtual Organization Organization { get; set; }
// [...]
}
public class Organization
{
public int ID { get; set; }
public string Name { get; set; }
public virtual List<User> Users { get; set; }
// [...]
}
Accessing the primary key (ID) through the Organization navigation property will cause the whole Organization entity to be loaded into the context:
foreach(var user in db.Users)
Console.WriteLine(user.Organization.ID);
Given that the OrganizationID foreign key is part of the User row, I should be able to access it without causing a Lazy Load of the whole entity (and indeed, NHibernate does this properly).
Short of adding properties for the foreign key IDs into all of my 100+ entities so I can access their values without loading the entities, is there anything to be done to avoid this behaviour?
EDIT: Furthermore, even doing a null check will cause a load of the Organization entity (not in NHibernate):
foreach(var user in db.Users)
Console.WriteLine(user.Organization != null);
I guess this is due to the fundamental differences in the way the entity proxy is implemented in these two frameworks. So I'll have to adapt all of my code to this new frustrating behaviour... Unless someone already had to go through this and could enlighten me?
Nope, you'll need to add them as property in your class (that is; if you want it strong typed) like this to access it directly.
public class User
{
public int ID { get; set; }
public string FullName { get; set; }
//added ID
public int OrganizationID { get; set; }
public virtual Organization Organization { get; set; }
// [...]
}
By accessing the int you'll prevent the lazy loading, EF will bind the ID through naming conventions. Having said that: 100+ classes... :|
UPDATE:
As I just realized; you might want to try:
db.Users
.Include("Organization.ID")
.Where(/*your stuff*/) //etc.;
I am not certain if it will fully load the nested property. If it doesn't, it might be a small performance gain.

Categories