I am creating a basic rating system where users will enter a movie we have watched together and review it with a rating out of 10. I am struggling with how to get all the data to be retrieve into a view model(needs more work but dev testing currently).
Controller:
[HttpGet("{id}")]
public async Task<ActionResult<EventViewModel>> GetEventWithReview(int id)
{
//not much here since I really am that stuck
if (#event == null)
{
return NotFound();
}
return null;
}
ViewModel:
public class EventViewModel
{
public Event Event { get; set; }
public List<Review> Reviews { get; set; }
}
I have models:
Event:
public class Event
{
public int Id { get; set; }
[ForeignKey("Event")]
public int EventTypeID { get; set; }
public DateTime? DateTime { get; set; }
public string EventName { get; set; }
}
EventType:
public class EventType
{
public int Id { get; set; }
public string Type { get; set; }
}
Review:
public class Review
{
public int Id { get; set; }
[ForeignKey("ApplicationUser")]
public int UserID { get; set; }
public int Rating { get; set; }
[ForeignKey("Event")]
public int EventID { get; set; }
}
First, for a view model, this should reflect the data that the view wants to consume rather than containing the entities. Entities should always reflect the data state and that is often more information, and a relational model of the data which the view doesn't really need. As a general rule, an entity should never be passed outside of the scope of the DbContext where it was retrieved. EF does support detached entities but these need to be used with care and generally cause a lot more problems than they are worth in a project.
The goal of a view model is to compact the data that the view needs which improves over the wire performance, and also protects your system from revealing too much about it's data structure.
public class EventViewModel
{
public int Id { get; set; }
public string EventType { get; set; }
public DateTime? DateTime { get; set; }
public string EventName { get; set; }
public ICollection<ReviewViewModel> Reviews { get; set; }
}
public class ReviewViewModel
{
public string UserName { get; set; }
public int Rating { get; set; }
}
Then when you go to select the event and it's reviews, you utilize something called Projection to translate the entities into the view models. This is done manually using Select() or can be automated by leveraging a library like AutoMapper which has a ProjectTo<T>() method.
Ideally your controller should have a DbContext injected, however as a starting point this example just scopes the DbContext in the request:
[HttpGet("{id}")]
public async Task<ActionResult<EventViewModel>> GetEventWithReview(int id)
{
using(var context = new YourAppDbContext())
{
var event = await context.Events
.Where(x => x.Id == id)
.Select(x => new EventViewModel
{
Id = x.Id,
EventType = x.EventType.Name,
DateTime = x.DateTime,
EventName = x.EventName,
Reviews = x.Reviews
.Select(r => new ReviewModel
{
UserName = r.User.Name,
Rating = r.Rating
}).ToList()
}).SingleAsync();
return View(event);
}
}
The projection here makes a few assumptions. For instance, if you have an EventType entity for the type of event, we may just want to display the event's Type as a string with the event so there's no need to create an event type view model, just Select the EventType.Name as "EventType" into the EventViewModel. ViewModels can flatten the relational data represented by the entities in this way to only pass the info the view can use rather than everything. The same thing was done to get a user's name for the review (Assuming a User navigation property on the Review) so if you wanted to list reviews you could have a user name, comment, and rating. If not needed, then you could even just use a List<int> for ratings and use x.Ratings.Select(r => r.Rating).ToList() to just get the rating #'s. You can further refine the query if you want to do things like retrieve the most recent 10 ratings or such in the query expression with OrderBy and Take. EF will compose that all down to SQL to only pull back the data necessary to populate the view model. (Faster and less memory used on the server) The advantage of projection is that you can query values from the related entities via their navigation properties as you need in the query expression without having to worry about eager loading /w Include(). EF will work out building a query to populate whatever you extract via Select.
Related
I'd like to retrieve data from my DB with two tables (Scientists and Countries).
This is how i programmed my class
public class Scientist
{
public long ScientistID { set; get; }
public string Name { set; get; }
public string Surname { set; get; }
public string BornDate { set; get; }
public string Subject { set; get; }
public long? CountryID { set; get; }
public virtual Country Country { set; get; }
}
public class Country
{
public long CountryID { set; get; }
public string CountryName { set; get; }
public string Zone { set; get; }
public virtual List<Scientist> Scientists { set; get; }
}
public class PeopleOfScienceContext : DbContext
{
public PeopleOfScienceContext() : base("ScientistsConnectionString")
{
Database.SetInitializer<PeopleOfScienceContext>(new CreateDatabaseIfNotExists<PeopleOfScienceContext>());
}
public DbSet<Scientist> Scientists { get; set; }
public DbSet<Country> Countries { get; set; }
}
Is there a "simple" way to download from DB both content since they're correlated?
I mean, with something like:
List<Scientist> scients = ctx.Scientists.ToList(); //ctx was initialized don't worry!
I can download all my data from Scientist table, but i cannot download the "CountryName" info since it is stored in another table and it remains blank.
I'd like to avoid to create a "JOIN" query; since i'm learning a Frame Work i've been told to "write the bare minimum" code. My second idea was to download both tables and merge them "client side", but still seems an useless complication of the task (and i bet this method couldn't scale well with big tables). Am I missing the simplest solution or there isn't such a thing in EF 6?
I believe the concept you are looking for is Eager Loading.
Although you could explicitly join the Scientist and Country tables in a Linq Query, because you already have the navigation property defined, you should be able to simply Include the navigation:
List<Scientist> scientists = ctx.Scientists
.Include(s => s.Country)
.ToList()
... or the async equivalent (since this is I/O bound work)
var scientists = await ctx.Scientists
.Include(s => s.Country)
.ToListAsync();
And you should now be able to dereference the country like so:
scientist.Country.CountryName
In the event of the relationship being optional (i.e. CountryId can be NULL in the database), then you could use the nullsafe dereference operator:
scientist.Country?.CountryName
I have an application that is written on the top of ASP.NET MVC 5 framework along with Entity 6 Framework. I am using Database-first approach instead on code-first approach.
I am aware on how to build simple relation between my models using virtual navigations like public virtual RelationModel Relation { get; set } or public virtual ICollection<RelationModel> Relations { get; set }
However, the requirement is more tricky this time. I need to be able to build a relation using composite key not just a single column. I want to add a relation where the joiner/join-clause on one side should be a computed property called LocalDate and DateOf on the other side AND OwnerId column == UserId of the other side.
Here is an example of my models
My parent model looks like the following
public Entry
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey("Owner")]
public int OwenerId { get; set; }
public DateTime StartedAt { get; set; }
public int UtcOffset { get; set; }
public virtual User Owner { get; set; }
// This puts the StartedAt in the correct date state which is needed for the relation
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime LocalDate
{
get
{
return StartedAt.AddSeconds(UtcOffset * -1).Date;
}
}
// This is the relation that needs a complex join clause
public virtual ICollection<Bucket> Buckets { get; set }
}
Here is my child model looks like the following
public Bucket
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int UserId { get; set; }
public DateTime DateOf { get; set; } //This is a date it or stored with 00:00:00.000 time in the database
public virtual User Owner { get; set; }
}
From my Entry model, I want to be able to access my Buckets relations using the following logic Entry.LocalDate == Bucket.DateOf && Entry.UserId == Bucket.UserId
Note that the LocalDate property is NOT a database column, rather a computed property
Is it possible to construct this kind of relation between my model where I can use .Include(x => x.Buckets) to get the relations accordingly? If so, how? If it is not possible, what are other ideas that can be used to deliver the same results?
I have a database made with Entity Framework. I have two tables Users and Advertisments and the relationship between them is Many-to-Many. Everything is working fine except when I want to return the number of ICollection in class Users.
[Table("Advertisments")]
public class Advertisment
{
public Advertisment()
{
Users = new HashSet<User>();
}
[Key]
public int AdvertismentID { get; set; }
public string Address { get; set; }
public string City { get; set; }
public double Rating { get; set; }
public int NumberOfRates { get; set; }
public string Title { get; set; }
public string PhoneNumber { get; set; }
public string Email { get; set; }
public double Latitude { get; set; }
public double Longitude { get; set; }
public ICollection<User> Users { get; set; }
}
[Table("Users")]
public class User
{
public User()
{
FavouriteAdvertisments = new HashSet<Advertisment>();
}
[Key]
public int UserID { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public ICollection<Advertisment> FavouriteAdvertisments { get; set; }
}
public class GetHiredDBContext : DbContext
{
public GetHiredDBContext()
: base("GetHiredDBContext")
{ }
public DbSet<User> Users { get; set; }
public DbSet<Advertisment> Advertisments { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>().HasMany(a => a.FavouriteAdvertisments).WithMany(u => u.Users).Map(m =>
{
m.MapLeftKey("UserID");
m.MapRightKey("AdvertismentID");
m.ToTable("UserAdvertisment");
});
}
}
And this is what I want to do:
public ICollection<Advertisment> favouriteAdvertismentsByUser(int UserID)
{
GetHiredDBContext db = new GetHiredDBContext();
foreach (User user in db.Users)
{
if (user.UserID == UserID)
{
return user.FavouriteAdvertisments;
}
}
return null;
}
Everytime I call this method, the number of elements in ICollection for every user is 0!
public ICollection<Advertisment> favouriteAdvertismentsByUser(int UserID)
{
GetHiredDBContext db = new GetHiredDBContext();
// First of all, you probably forgot to "include" FavouriteAdvertisments
var users = db.Users.Include(u => u.FavouriteAdvertisments);
// Second of all, use linq!
return users.SingleOrDefault(u => u.UserID == UserID).FavouriteAdvertisments;
}
If your using Entity Framework you need to write your queries in Linq so the query provider can translate that into a SQL statement. As you have it now it is doing a table scan. Instead try this:
public ICollection<Advertisment> favouriteAdvertismentsByUser(int UserID)
{
return new GetHiredDbContext()
.Users
.Single(u => u.UserID = UserID)
.FavouriteAdvertisements;
}
One thing to note, this method now expects there to be exactly 1 record in your table with that UserID. It will throw an exception if it does not exist. Personally I prefer this because if I'm calling a method I expect it to work, and exception would mean I coded something wrong allowing me to find bugs earlier. You also do not have to check if your collection is null before getting the count.
The way your entites are currently set up, you will have to either use Eager, or Explicit loading, your related entites will not be loaded automatically.
To Explicitly load, I believe you can use your original query (provided you're passing an entity that can be found in the DBSet, and make an explicit call to load the related information (using Load):
E.g. (provided your entity can be found).
public ICollection<Advertisment> favouriteAdvertismentsByUser(User userEntity)
{
// Load the blog related to a given post
GetHiredDBContext db = new GetHiredDBContext();
db.Entry(userEntity).Reference(p => p.FavouriteAdvertisments).Load();
return user.FavouriteAdvertisments;
}
Although it's probably cleaner to obtain your entity from your context, and call load on that, rather than interating through your entire set.
To Eagerly load, you make your load request at the time of query, using Include:
public ICollection<Advertisment> favouriteAdvertismentsByUser(int userID)
{
GetHiredDBContext db = new GetHiredDBContext();
User myUser = db.Users
.Where(x => x.UserID = userID)
.Include(x => x.FavouriteAdvertisments)
.FirstOrDefault();
return myUser.FavouriteAdvertisments;
}
To obtain the data the third way, using Lazy-Loading, you would have to make some alterations to your classes, namely marking your ICollection navigation properties as virtual, so entity framework is able to create valid proxy types for your classes. Your data would be available as required, and loaded on demand.
Hopefully I haven't got the problem completely wrong, just about to shut down/sleep.
Good luck.
More info: http://msdn.microsoft.com/en-us/data/jj574232.aspx
I’m currently working on an n-tier web project. After researching into Data Transfer Objects and their benefits we decided to give this pattern a go. Our ASP.NET MVC website does not have direct access to the EF DbContext but instead will use DTOs to send and receive entity data. There will be a service/mapping layer that will convert between DTOs and entity models.
My question is, what is the best way to translate entity model navigation properties into its DTO?
Below is an example of a entity model and its DTO from the project:
Entity Model:
public class Payment
{
public int ID { get; set; }
public DateTime? PaidOn { get; set; }
public decimal Amount { get; set; }
public string Reference { get; set; }
//Navigation Properties
public virtual PaymentMechanism PaymentMechanism { get; set; }
public virtual ICollection<Order> Orders { get; set; }
}
DTO:
public class PaymentDto
{
public int ID { get; set; }
public DateTime? PaidOn { get; set; }
public decimal Amount { get; set; }
public string Reference { get; set; }
//--------Navigation Properties - Object Ids--------
public int PaymentMechanismId { get; set; }
public ICollection<int> OrderIds { get; set; }
}
As can be seen they are very similar except for the navigation properties. I have changed them to hold integer Ids (of the entities) instead of the entity models. Therefore if the navigation property entities need to be obtained, their Id’s can passed into a service/mapping layer function which will retrieve the entities from then database, map them to DTOs and return the collection. Is this an acceptable way of doing things?
I am new to this area so some of my terminology might not be totally correct but hopefully you’ll understand what I'm getting at. If you need me to clarify or provide additional detail on anything, please let me know.
You can load the DTOs using a projection:
var paymentDtos = context.Payments
.Where(p => p.Amount >= 1000m) // just an example filter
.Select(p => new PaymentDto
{
ID = p.ID,
PaidOn = p.PaidOn,
Amount = p.Amount,
Reference = p.Reference,
PaymentMechanismId = p.PaymentMechanism.ID,
OrderIds = p.Orders.Select(o => o.ID)
})
.ToList();
You have to declare the OrderIds in the dto as IEnumerable<int> though, not as ICollection<int> to make this compile.
I'm not sure if this key collection is really useful. If you want to load the orders later you could do it in a separate service method just based on the ID of the Payment, like so:
public IEnumerable<OrderDto> GetPaymentOrders(int paymentID)
{
return context.Payments
.Where(p => p.ID == paymentID)
.Select(p => p.Orders.Select(o => new OrderDto
{
ID = o.ID,
//etc. mapping of more Order properties
}))
.SingleOrDefault();
}
I'm usually using Automapper for this kind of scenario. I would create a Dto class form my main entity and also Dto's for my navigation property entities, then let Automapper do the mapping automatically, without having to write the mapping code manually.
public class PaymentDto
{
public int ID { get; set; }
public DateTime? PaidOn { get; set; }
public decimal Amount { get; set; }
public string Reference { get; set; }
//Navigation Properties
public virtual PaymentMechanismDto PaymentMechanism { get; set; }
public virtual ICollection<OrderDto> Orders { get; set; }
}
public class PaymentMechanismDto
{
//properties
}
public class OrderDto
{
//properties
}
public class MappingProfile : Profile
{
public MappingProfile()
{
Mapper.CreateMap< Payment, PaymentDto >();
Mapper.CreateMap< PaymentMechanism, PaymentMechanismDto >();
Mapper.CreateMap< Order, OrderDto >();
}
}
I think I have read every article and stack overflow question regarding this, but cannot work out the solution. Let me start out with my models
public class Entry
{
public Entry ()
{
DateEntered = DateTime.Now;
}
public int Id { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string Email { get; set; }
public string Number { get; set; }
public string FbId { get; set; }
[ReadOnly(true)]
public DateTime DateEntered { get; set; }
public string AccessToken { get; set; }
//Relationsips
public Backgrounds Background { get; set; }
public Cars Car { get; set; }
}
public class Backgrounds
{
public int Id { get; set; }
public string Name { get; set; }
public string Filename { get; set; }
}
public class Cars
{
public int Id { get; set; }
public string Name { get; set; }
public string FileName { get; set; }
}
Now in my controller, I am updating the entry. Like follows
// PUT /api/entries/5
public HttpResponseMessage Put(Entry entry)
{
if(ModelState.IsValid)
{
_db.Entries.Attach(entry);
_db.Entry(entry).State = EntityState.Modified;
_db.SaveChanges();
return new HttpResponseMessage(HttpStatusCode.NoContent);
}
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
My Entry model gets updated correctly, but if for eg entry.Background.Name changes, this will not be persisted to the database. My controller is accepting the entire entry model including its relationships => Backgrounds and Cars. However any value that is changed to the relationship is not updated or reflected. Any elegant solution without having to query the database then updating? I dont want to have any extra queries or lookups before I update.
Thanks
Tyrone
You must manually tell EF about all changes done to the object graph. You told EF just about change to entry instance but you didn't tell it about any change to related entities or relations itself. There is no elegant way to solve this. You have generally two options:
You will use some DTOs instead your entities and these DTOs will have some flag like IsDirty - when you receive object graph back to your controller you will reconstruct entities from DTOs and set their state based on IsDirty. This solution needs further extensions for example if your client can also delete relations.
You will query object graph from database and merge your incoming changes to entities retrieved from database.
There are some partial solutions like forcing to save changes to all related objects by setting their state to modified and identifying new objects by Id == 0 but again these solutions work only in specific scenarios.
More complex discussion about this problem.