How to create clustered keys in code first - c#

A person can have many colleagues and a colleague is a person. Is it possible to make a clustered key on ColleagueId and PersonId?
Ok, im not really sure but the Colleague class is only there because the database should understand the connection to Person. So, actually i dont need the Colleague class. How can I do so the database understands that the Person has a list of Colleagues that is a Person?
In the program, we can create Persons and then we should be able to add other Persons to the Persons list of Colleagues!
My explanation feels cloudy, but I do not know how to explain it in any other way.
Colleague class:
public class Colleague
{
[Key]
public int ColleagueId { get; set; }
[Key]
public virtual Person PersonId { get; set; }
}
Person class:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public byte[] Image { get; set; }
public virtual ICollection<Conversation> Conversations { get; set; }
public virtual ICollection<Colleague> Colleague { get; set; }
public Person()
{
Conversations = new List<Conversation>();
Colleague = new List<Colleague>();
}
}

I use OrmLite v3 (which does not support complex primary keys). The work around I use there is to create a new property that is a combination of both keys and which I use as a key:
public class Colleague
{
public int ColleagueId { get; set; }
public int PersonId { get; set; }
[Key]
public string CombinedId {
get { return ColleagueId.ToString() + PersonId.ToString(); }
}
}
I use the string type to avoid getting the sum of the two int values (which can lead to key collisions, although is is not completely safe, depending on the int digits numbers). Of course you can adapt this according to your own types.

You basically want a many-to-many relationship between Person entities. For this you would need a junction table which links pairs of Person entities. If we call this entity WorkRelationship:
class WorkRelationship
{
[Key]
[Column(Order = 1)]
[ForeignKey("Myself")]
public int MyselfId { get; set; }
public virtual Person Myself { get; set; }
[Key]
[Column(Order = 2)]
[ForeignKey("Colleague")]
public int ColleagueId { get; set; }
public virtual Person Colleague { get; set; }
}
Then we modify Person like so:
class Person
{
public int Id { get; set; }
public string Name { get; set; }
[InverseProperty("Myself")]
public virtual ICollection<WorkRelationship> WorkRelationships { get; set; }
public void AddWorkRelationship(Person colleague)
{
if (WorkRelationships == null)
{
WorkRelationships = new List<WorkRelationship>();
}
WorkRelationships.Add(new WorkRelationship { Myself = this, Colleague = colleague });
}
}
So you can see that Person now has a collection of WorkRelationships: by adding an entity to this collection, you not only link this person to his/her colleague, but also create the inverse relationship. I've also added a helper method so that you can easily add a relationship.
Here's a very basic database context class that can be used to manage these entities:
sealed class MyContext : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<WorkRelationship> WorkRelationships { get; set; }
public IEnumerable<Person> GetColleagues(int personId)
{
List<WorkRelationship> relationships =
WorkRelationships
.Include(x => x.Myself)
.Include(x => x.Colleague)
.Where(x => x.MyselfId == personId || x.ColleagueId == personId)
.ToList();
foreach (WorkRelationship relationship in relationships)
{
if (relationship.Myself.Id == personId)
{
yield return relationship.Colleague;
}
else if (relationship.Colleague.Id == personId)
{
yield return relationship.Myself;
}
}
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
}
}
I've also added a helper method to this that will retrieve the colleagues of a given person.
We can now create a simple test program to insert and query people:
static void Main(string[] args)
{
var alice = new Person { Name = "Alice" };
var bob = new Person { Name = "Bob" };
var colin = new Person { Name = "Colin" };
var davina = new Person { Name = "Davina" };
alice.AddWorkRelationship(bob);
alice.AddWorkRelationship(colin);
bob.AddWorkRelationship(davina);
using (var db = new MyContext())
{
db.People.AddRange(new[] { alice, bob, colin, davina });
db.SaveChanges();
}
using (var db = new MyContext())
{
Console.WriteLine("Bob works with:");
foreach (Person colleague in db.GetColleagues(bob.Id))
{
Console.WriteLine(colleague.Name);
}
}
Console.ReadLine();
}
Original answer below (included for context)
If a Colleague is-a Person, then you should model it like that:
public class Colleague : Person
{
// don't need any other properties based on what you've posted
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public byte[] Image { get; set; }
public virtual ICollection<Conversation> Conversations { get; set; }
public virtual ICollection<Colleague> Colleagues { get; set; }
}
(I have pluralized the property name used to hold the collection of colleagues).
EF Code First should then create a single Person table with an extra Discriminator column used to distinguish between Person and Colleague entities.
Thinking about it a little more, I'm not even sure you need the separate Colleague entity. You could probably get away with this:
public class Person
{
...
public virtual ICollection<Person> Colleagues { get; set; }
}
Note ICollection<Person> instead of ICollection<Colleague>.

Related

Reference navigation property not loading (Entity Framework)

I got 3 models: Human, Skill and HumanSkill. There is a many to many relationship between Human and Skill, the HumanSkill is the intermediary table between them.
My query to the database loads the collection of the intermediary table HumanSkill correctly, but does not load the reference navigation property Skill through which I want to load the Skill name (Human -> HumanSkill -> Skill -> Skill.name) using a query projection with select.
public IActionResult Preview(int humanId)
{
var currentHuman = this.db.Humans
.Where(x => x.Id == humanId)
.Select(r => new HumanPreviewViewModel
{
PrimaryData = r.PrimaryData,
// How should I write this line?
Skills = r.Skills.ToList(),
}).SingleOrDefault();
return View(currentResume);
}
Human model:
public class Human
{
public Human()
{
this.Skills = new HashSet<HumanSkill>();
}
public int Id { get; set; }
public virtual PrimaryData PrimaryData { get; set; }
public virtual ICollection<HumanSkill> Skills { get; set; }
}
HumanSkill model:
public class HumanSkill
{
public int Id { get; set; }
public int HumanId { get; set; }
public Human Human { get; set; }
public int SkillId { get; set; }
public Skill Skill { get; set; }
}
Skill model:
public class Skill
{
public Skill()
{
this.Humans = new HashSet<HumanSkill>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<HumanSkill> Humans { get; set; }
}
HumanPreviewViewModel:
public class HumanPreviewViewModel
{
public HumanPreviewViewModel()
{
}
public PrimaryData PrimaryData { get; set; }
public List<HumanSkill> Skills { get; set; }
}
}
How can I achieve this without using include?
If you use some data from Skills table in the Select, EF will perform the necessary joins to retrieve the data
var currentHuman = this.db.Humans
.Where(x => x.Id == humanId)
.Select(r => new HumanPreviewViewModel
{
PrimaryData = r.PrimaryData,
SkillNames = r.Skills.Select(hs => hs.Skill.Name).ToList(),
}).SingleOrDefault();
When projecting from entity to a view model, avoid mixing them. For example, do not have a view model contain a reference or set of entities. While it might not seem necessary, if you want a list of the skills with their ID and name in the HumanPreviewViewModel then create a serialize-able view model for the skill as well as the PrimaryData if that is another related entity. Where PrimaryData might be a one-to-one or a many-to-one the desired properties from this relation can be "flattened" into the view model.
[Serializable]
public class HumanPreviewViewModel
{
public Id { get; set; }
public string DataPoint1 { get; set; }
public string DataPoint2 { get; set; }
public List<SkillViewModel> Skills { get; set; }
}
[Serializable]
public class SkillViewModel
{
public int Id { get; set; }
public string Name { get; set; }
}
Then when you go to extract your Humans:
var currentHuman = this.db.Humans
.Where(x => x.Id == humanId)
.Select(r => new HumanPreviewViewModel
{
Id = r.Id,
DataPoint1 = r.PrimaryData.DataPoint1,
DataPoint2 = r.PrimaryData.DataPoint2,
Skills = r.Skills.Select(s => new SkillViewModel
{
Id = s.Skill.Id,
Name = s.Skill.Name
}).ToList()
}).SingleOrDefault();
The reason you don't mix view models and entities even if they share all of the desired fields is that your entities will typically contain references to more entities. When something like your view model gets sent to a Serializer such as to send to a client from an API or due to a page calling something an innocent looking as:
var model = #Html.Raw(Json.Encode(Model));
then the serializer can, and will touch navigation properties in your referenced entities which will trigger numerous lazy load calls.

Automapper one-to-many relationship

I have a method that creates a Course. The Course retains the Teacher and their id. But after adding the Course, TeacherID has the value but Teacher has null. I think the problem is in the mapping. CourseAddRequest only has a teacherID, how can I add a Teacher?
AddCourse:
public CourseResponse AddCourse(CourseAddRequest courseAddRequest, Guid teacherId)
{
var teacher = _uniDbContext.Teachers
.Include(t => t.Courses)
.SingleOrDefault(t => t.Id == teacherId);
if (teacher == null)
throw new Exception("User doesn't exist");
var course = _mapper.Map<Course>(courseAddRequest);
teacher.Courses.Add(course);
_uniDbContext.Teachers.Update(teacher);
_uniDbContext.Courses.Update(course);
_uniDbContext.Courses.Add(course);
_uniDbContext.SaveChanges();
return _mapper.Map<CourseResponse>(course);
}
Course:
public class Course : BaseEntity
{
public string Header { get; set; }
public string Description { get; set; }
public Guid TeacherId { get; set; }
public Teacher Teacher { get; set; }
public List<Student> StudentsOnCourse { get; set; } = new List<Student>();
}
CourseResponse:
public class CourseResponse
{
public Guid Id { get; set; }
public string Header { get; set; }
public string Description { get; set; }
public TeacherResponse Teacher { get; set; }
public Guid TeacherId { get; set; }
public IEnumerable<StudentResponse> Students { get; set; }
}
CourseAddRequest:
public class CourseAddRequest
{
public string Header { get; set; }
public string Description { get; set; }
public Guid TeacherId { get; set; }
}
CourseProfile:
public class CourseProfile : Profile
{
public CourseProfile()
{
CreateMap<CourseAddRequest, Course>();
CreateMap<Course, CourseResponse>();
}
}
TeacherProfile:
public class TeacherProfile : Profile
{
public TeacherProfile()
{
CreateMap<TeacherAddRequest, Teacher>();
CreateMap<Teacher, TeacherResponse>();
}
}
You guessed right. When you execute the mapping from CourseAddRequest to Course it has no Teacher Therefore the Course teach will be null.
var course = _mapper.Map<Course>(courseAddRequest);
Assuming you're using EntityFramework or another ORM it'll be able to do the insertion correctly due to the existence of the Teacher that you've referenced in the Course via TeacherId property.
And while you add the Course to the teacher's in line 11 of your method, This still leaves the Teacher property null in course. As a result, when you map it to CourseResponse you get null.
There's two way to fix this, First, you can add the teach to your course object So the mapper finds the teacher before mapping to CourseResponse in the return statement.
course.Teacher = teacher;
Or map the teacher object directly to the response.
var courseResponse = _mapper.Map<CourseResponse>(course);
courseResponse.Teacher = _mapper.Map<TeacherResponse>(teacher);
return courseResponse;

EF Core Mapping Entities to Models

I'm trying to efficiently map entities on to models.
My entities are:
public class ParentEntity
{
public int Id { get; set; }
public string Name { get; set; }
public ChildEntity Child { get; set; }
}
public class ChildEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
and my models are:
public class ParentModel
{
public int Id { get; set; }
public string Name { get; set; }
public ChildModel Child { get; set; }
}
public class ChildModel
{
public int Id { get; set; }
public string Name { get; set; }
}
(In practice, there would be differences between these classes, but not here for simplification.)
I've written an extension method to do the mapping:
public static IQueryable<ParentModel> ToParentModel (this IQueryable<ParentEntity> parentEntities)
{
return parentEntities.Select(p => new ParentModel
{
Id = p.Id,
Name = p.Name,
Child = new ChildModel { Id = p.Child.Id, Name = p.Child.Name.ToLower()}
});
}
The ToLower() is there to highlight the problem.
I can run this with:
var parents = _context.Set<ParentEntity>().ToParentModel().ToArray();
The generated SQL is:
SELECT "p"."Id", "p"."Name", "c"."Id", lower("c"."Name") AS "Name"
FROM "Parents" AS "p"
LEFT JOIN "Children" AS "c" ON "p"."ChildId" = "c"."Id"
i.e. the lowercase processing is done in the database.
All good so far, except that the separation of concerns is not good. The code to initialize a ChildModel is in the same place as the code to initialize a ParentModel.
I try using a constructor in ChildModel:
public ChildModel(ChildEntity ent)
{
Id = ent.Id;
Name = ent.Name.ToLower();
}
and in the extension method:
return parentEntities.Select(p => new ParentModel
{
Id = p.Id,
Name = p.Name,
Child = new ChildModel (p.Child)
});
This works, but the generated SQL does not do contains a lower. The conversion to lowercase is done in the program.
Is there a way I can have by cake and eat it?
Can I still have my C# code converted to SQL, but still structure my C# code in a modular way?

EntityFramework: How to load List of objects from database?

I have two entities:
public class Booking
{
[Key]
public int Id { get; set; }
public int RoomId { get; set; }
[ForeignKey("RoomId")]
public Room Room { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string DocumentNumber { get; set; }
public string ContactPhone { get; set; }
}
public class Room
{
[Key]
public int RoomId { get; set; }
public int Number { get; set; }
public int Size { get; set; }
public bool HasBalcony { get; set; }
public int Beds_1 { get; set; }
public int Beds_2 { get; set; }
public double DayPrice { get; set; }
public List<Booking> Bookings { get; set; }
...
public int BookingsCount()
{
return Bookings.Count;
}
public bool IsFree(DateTime dateTime)
{
MessageBox.Show(BookingsCount().ToString());
return true;
}
}
and DbContext:
public class HotelContext : DbContext
{
public DbSet<Employee> Employees { get; set; }
public DbSet<Room> Rooms { get; set; }
public DbSet<Booking> Bookings { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Booking>()
.HasRequired(b => b.Room)
.WithMany(r => r.Bookings)
.HasForeignKey(b => b.RoomId);
}
}
When MessageBox.Show is called I'm getting exception: An unhandled exception of type 'System.NullReferenceException' occurred in Hotel.exe
When I'm trying to access Room::Bookings, the list is always null. There is one row in Bookings table and multiple rows in Rooms table.
How can I load all of Bookings into Room object?
Depends where you are in the learning curve, however some things stand out
Firstly
You either want to create a relationship via FluentApi or Annotations, not both
Ie. you have this on your Room entity
[ForeignKey("RoomId")]
And this in fluent
modelBuilder.Entity<Booking>()
.HasRequired(b => b.Room)
.WithMany(r => r.Bookings)
.HasForeignKey(b => b.RoomId);
You need to pick one or the other, otherwise you may end-up with multiple Ids in your Booking i.e RoomId and Room_Id
Secondly
If you want to be able to Lazy Load bookings you need to make Bookings collection Virtual
public virtual List<Booking> Bookings { get; set; }
Lastly
To access your data (presuming your connection string is correct)
using(var db = new HoteContext())
{
var rooms = db.Rooms.Include(x => x.Bookings).ToList();
}
Note : Although EF Lazy loads relationships, you might want to make sure you have included the Room->Booking relationship
Consider the following code.
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
using (MyDbContext dbContext = new MyDbContext())
{
dbContext.Departments.Add(new Department()
{
Name = "Some Department1",
Employees=new List<Employee>()
{
new Employee() { Name = "John Doe" }
}
});
dbContext.SaveChanges();
var department = dbContext.Departments.FirstOrDefault(d => d.Name == "Some Department1");
if (department.Employees != null)
{
foreach (var item in department.Employees)
{
Console.WriteLine(item.Name);
}
}
}
}
}
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
public List<Employee> Employees { get; set; }
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
}
public class MyDbContext : DbContext
{
public DbSet<Department> Departments { get; set; }
public DbSet<Employee> Employees { get; set; }
}
}
If you have the code in above way, the control will not go into if condition, because department.Employees is null. Now, change the Department entity as follows.
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<Employee> Employees { get; set; }
}
And now you should be able to see control go into if condition and outputs the employees name.
That is called Lazy Loading.
If you want to eagerly load, you don't have to put virtual to the property. You can Include the properties as follows.
var department = dbContext.Departments.Include(d => d.Employees).FirstOrDefault(d => d.Name == "Some Department1");
Now you can see the employees names are getting outputted.
You will absolutely run into performance trouble with your design here.
The temptation with EF is to completely map your object model to the DB and have EF do all the magic for you behind the scenes. But you need to think about it in terms of only getting specifically what you need from the db at any point in time. Otherwise you will get all kinds of cartesian product issues. I highly suggest you get yourself a copy of Hibernating Rhino's EF Profiler or similar so you can analyze your code statically and at runtime for EF performance issues (and see what SQL it is generating). For this what you want is a purpose built call to the DB to get the count. Otherwise what will happen is you will pull the entire table of Bookings and then have C# give you the count. That only makes sense if you want to do something with the whole list. Two options would be:
1) Create a VIEW against the Bookings table and map that to EF. The view would look something like SELECT ROOMS.ROOMID, COUNT(*) - you map this view to your model and voila now you have a list of counts by room (id) and you can use them individually or sum it up to get your total count for all rooms. If you have 1,000 bookings in 10 rooms, you are getting back only 10 rows from the DB. Whereas with your design, you are pulling back all 1,000 bookings with all their fields and then filtering down in C#. Bad juju.
2) The architecturally and conceptually simpler approach is going to be to do a direct query as such (obviously this returns only a single int from the db):
public int BookingsCount()
{
int count = 0;
try
{
using (var context = new HotelContext())
{
var sql ="SELECT COUNT(*) FROM Bookings WHERE ROOMID=" + this.RoomId;
count = context.Database.SqlQuery<int>(sql).First();
}
} catch (Exception ex)
{
// Log your error, count will be 0 by default
}
return count;
}
A simple solution would be making the Bookings property virtual.
public class Room
{
[Key]
public int RoomId { get; set; }
public int Number { get; set; }
public int Size { get; set; }
public bool HasBalcony { get; set; }
public int Beds_1 { get; set; }
public int Beds_2 { get; set; }
public double DayPrice { get; set; }
public virtual List<Booking> Bookings { get; set; }
}
More information on Entity Framework Loading Related Entities,
https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx

Entity Framework, randomly creating duplicate records

I have a simple entity framework 6 code first from existing database project for my web application. When I save data sometimes it saves properly with only 1 record saved. However sometimes it saves 2, 3 5 records it appears random.
For simplicity sake I have the following 2 classes. One is a parent "Person", and "PersonAddress" is the child. In my application there will always be 2 child records to 1 parent. No more no less (dont ask why). Here are my classes which are bare bones.
[Table("Person")]
public partial class Person
{
[Key]
public int PersonID { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
[Required]
public virtual ICollection<PersonAddress> PersonAddresses { get; set; }
}
[Table("PersonAddress")]
public partial class PersonAddress
{
[Key]
public int PersonAddressID { get; set; }
public int PersonID { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zipcode { get; set; }
}
Here is my DBContext class
public partial class MyDBContext : DbContext
{
public MyDBContext()
: base("name=MyDBContext")
{
//skips database initialization so it wont track changes and produce error, not needed for code first
Database.SetInitializer<MyDBContext>(null);
}
public virtual DbSet<Person> Persons { get; set; }
public virtual DbSet<PersonAddress> PersonAddresses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>().Property(x => x.PersonID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<PersonAddress>().Property(x => x.PersonAddressID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
}
//sample code
Person person = new Person();
person.FirstName = "TestFName";
person.LastName = "TestLName";
List<PersonAddress> addresses = new List<PersonAddress>();
PersonAddress address1 = new PersonAddress();
address1.Address1 = "line1"
//etc
//etc
addresses.Add(address1);
PersonAddress address2 = new PersonAddress();
address1.Address2 = "line1"
//etc
//etc
addresses.Add(address2);
//now add addresses to Person
person.PersonAddresses = addresses;
using (var context = new MyDBContext())
{
context.Persons.Add(person);
context.SaveChanges();
obj.PersonID = obj.PersonID;
}
What am I doing wrong, the data always gets saved and the child records are automatically added when I save the parent without issue. But as previously stated sometimes numerous sets of records are saved and I dont see any reason why. Thanks
Try this:
using (var context = new MyDBContext())
{
Person.PersonAdresses.add(addres1);
Person.PersonAdresses.add(addres2);
context.Entry(Person).State=EntityState.Added;
context.SaveChanges();
};

Categories