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.
Related
I am struggling a bit to wrap my head around Entity Framework and It's driving me crazy. I have an target object that I'd like to populate:
public class ApiInvitationModel
{
public int Id { get; set; }
public EventModel Event { get; set; }
public UserModel InvitationSentTo { get; set; }
public UserModel AttendingUser { get; set; }
}
The schemas of the above models are:
public class EventModel {
public int Id? { get; set; }
public string Name { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set }
public OrganizationModel HostingOrganization { get; set; }
public Venue Venue { get; set; }
public string Price { get; set; }
}
public class UserModel {
public int Id? { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public string MobileNumber { get; set; }
public List<OrganizationModel> Organizations { get; set; }
}
public class OrganizationModel {
public int Id? { get; set; }
public stirng Name { get; set; }
public string Address { get; set; }
public UserModel PrimaryContact { get; set; }
}
The above schemas are simplified for the purpose of the question and are the models we intend to return via API.
The problem is the origin schemas in the database is very different and I'm trying to map the database objects to these objects via Entity Framework 6.
My attempted solution was to try and nest the models via a query but that didn't work and I'm not sure where to go from here besides making numerous calls to the database.
public List<ApiInvitationModel> GetInvitations(int userId) {
using (var entities = new Entities()) {
return entities.EventInvitations
.Join(entities.Users, invitation => invitiation.userId, user => user.id, (invitation, user) => new {invitation, user})
.Join(entities.Events, model => model.invitation.eventId, ev => ev.id, (model, ev) => new {model.invitation, model.user, ev})
.Join(entities.organization, model => model.user.organizationId, organization => organization.id, (model, organization) => new ApiInvitationModel
{
Id = model.invitation.id,
Event = new EventModel {
Id = model.event.id,
Name = model.event.name,
StartDate = model.event.startDate,
EndDate = model.event.endDate,
HostingOrganization = new OrganizationModel {
Id = model.invitation.hostingId,
Name = model.event.venueName,
Address = model.event.address,
PrimaryContact = new UserModel {
Name = model.event.contactName,
PhoneNumber = model.event.contactNumber,
}
}
...
},
InvitedUser = {
}
}
).ToList();
}
}
As you can see above, there's quite a bit of nesting going on but this doesn't work in Entity Framework 6 as far as I am aware. I keep getting the following errors:
"The type 'Entities.Models.API.UserModel' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order.",
Based on the above error, I assumed that each of the model initiatilizations would need to be the same (i.e. initializing the values as the same ApiInvitationModel in each join in the same order) but that produces the same error.
What would be the best approach to handling this, keepign in mind the source database doesn't have foreign keys implemented?
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?
I have created 3 tables relation (users, projects, products)
one user has many projects and one project has many products (one to many)
I need to show all the projects and contained products on user login
I have done it using the following code but I don't think this is best way to deal with it. I need to do it better
public ActionResult Index()
{
ModulesViewModel mvm = new ModulesViewModel();
List<Modules> modules = new List<Modules>();
var userId = User.Identity.GetUserId();
var projects = _adsDbContext.Project.Where(x=>x.UserID == userId).ToList();
foreach (var pro in projects)
{
var productData = _adsDbContext.Product.Where(x => x.ProjectID == pro.ProjectID);
modules.AddRange(productData);
}
modules = modules.OrderBy(x => x.ProjectID).OrderBy(x=>x.ModuleNumber).ToList();
mvm.Modules = modules;
return View(mvm);
}
public class Project
{
public int ProjectID { get; set; }
public string Name { get; set; }
public virtual ICollection<ProductData> Products { get; set; }
public string UserID { get; set; }
public virtual ApplicationUser ApplicationUser { get; set; }
}
public class ProductData : Modules
{
public int ProductDataID { get; set; }
public float ConversionRate { get; set; }
public float Price { get; set; }
public float TotalSales { get; set; }
public float GrossSales { get; set; }
public float NetProfit { get; set; }
public float ProfitPerLead { get; set; }
}
public abstract class Modules
{
public int ProjectID { get; set; }
public virtual Project Project { get; set; }
}
This works fine but I need to do it in better way rather to create relation from scratch or make the query better.
Your model contains navigation property for each end of the project to product one-to-many relationship.
This allows you to start the query from the project, apply the filter and then "navigate" down using the collection navigation property and SelectMany:
var modules = _adsDbContext.Project
.Where(x => x.UserID == userId)
.SelectMany(x => x.Products) // <--
.OrderBy(x => x.ProjectID).ThenBy(x => x.ModuleNumber)
.ToList<Modules>();
or you can start the query from the product and use the reference navigation property to "navigate" up for applying the filter:
var modules = _adsDbContext.Product
.Where(x => x.Project.UserID == userId) // <--
.OrderBy(x => x.ProjectID).ThenBy(x => x.ModuleNumber)
.ToList<Modules>();
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
How can i select all levels of a self-referencing table as a view model. if max level was 2 or 3 then i can do that by calling Select multiple times but i have 4-5 level menus and i think there should be a better solution for doing that and select all levels.
this is my viewmodel:
public class MenuViewModel
{
public MenuViewModel()
{
Childs = new HashSet<MenuViewModel>();
}
public int Id{ get; set; }
public string Title { get; set; }
public string Url { get; set; }
public ICollection<MenuViewModel> Childs { get; set; }
}
and this is my Menu class:
public class Menu
{
public Menu()
{
Childs = new HashSet<Menu>();
}
public int Id{ get; set; }
public string Title { get; set; }
public string Url { get; set; }
public string Description { get; se; }
public byte[] Icon { get; set; }
public int Order { get; set; }
public ICollection<Menu> Childs { get; set; }
}
var viewModel = _dataContext.Menus
.Select(x => new MenuViewModel
{
Id = x.Id,
Title = x.Title,
Child = ???
}
.ToList();
When you are using EF , you can do like following way:
public class BlogComment
{
public int Id { set; get; }
[MaxLength]
public string Body { set; get; }
public virtual BlogComment Reply { set; get; }
public int? ReplyId { get; set; }
public ICollection<BlogComment> Children { get; set; }
}
using (var ctx = new MyContext())
{
var list = ctx.BlogComments
//.where ...
.ToList() // fills the childs list too
.Where(x => x.Reply == null) // for TreeViewHelper
.ToList();
}
with this way you don't need to use recursive queries but As far as I know,when use view model for fetch data , the dynamic proxy of EF Is destroyed.
about above example:
just select one list of comments and with
.Where(x=>x.Reply==null).Tolist()
EF fill children property of Comments.
Reference
Assuming that Id property is unique you can do it in two passes:
Create viewmodel items without children, but with associated children ids. From that data create the Dictionary that will allow you to get any viewmodel by its id. Values in this dictionary will be the created viewmodels alongside their children ids.
For each viewmodel item get the associated view model items using the children ids.
Something like:
var tempModels = _dataContext
.Menus
.Select(menu => new
{
childrenIds = menu.Childs.Select(item => item.Id).ToArray(),
viewModel =
new MenuViewModel
{
Id = menu.Id,
Title = menu.Title
}
})
.ToDictionary(
keySelector: item => item.viewModel.Id);
var viewModels = tempModels
.Select(kv =>
{
var viewModel = kv.Value.viewModel;
viewModel.Childs = kv
.Value
.childrenIds
.Select(childId =>
tempModels[childId].viewModel)
.ToList();
return viewModel;
})
.ToList();
for depth problem you can use one int property like Depth in your Model then you can fetch data like this :
public class BlogComment
{
public int Id { set; get; }
[MaxLength]
public string Body { set; get; }
public int Depth{get;set}
public virtual BlogComment Reply { set; get; }
public int? ReplyId { get; set; }
public ICollection<BlogComment> Children { get; set; }
}
using (var ctx = new MyContext())
{
var list = ctx.BlogComments
.Where(a=>a.Depth<2)
.ToList() // fills the childs list too
.Where(x => x.Reply == null) // for TreeViewHelper
.ToList();
}
for using viewModel in this senario , I Test with AutoMapper,but when select data with viewModel , the dyamic proxy that EF generate is Destroyed .
Please Note this Issue