Im trying to figure out a nice way to populate a nested collection model. I have the following in my viewmodel.
public class ListViewModel
{
public ICollection<Wish> Wishes { get; set; }
}
The Wish model looks like this:
public class Wish
{
public ICollection<Image> Images { get; set; }
}
Now in my controller I want to populate the ListViewModel with wishes also populate each wish with their corresponding images. What I have so far:
public IActionResult Index()
{
ICollection wishes = _repoWish.GetAllAsync().Result;
ICollection images = _repoImage.GetAllAsync().Result;
var model = new ListViewModel
{
Wishes = wishes
};
return View(model);
}
I know I can make a lot of foreach statements but I want to make use of LINQ to populate each wish with their corresponding images.
**I do have a generic repository class which makes it possible for me to retrieve all images in the same manner as the wishes.
*** Think about the repositories as contexts.
So instead of _repoWish and _repoImage its wishContext and imageContext
I am using ASP.NET Core 2.0 with Entity Framework Core
To load the related entities, you need to explicitly use the Include method call when you query the Wishes collection to eager load the Images property.
Also make sure you await your async calls.
var wishesWithImages = await yourDbContext.Wishes
.Include(g => g.Images)
.ToListAsync();
The variable wishesWithImages will be a collection of Wish objects with Images property loaded. You can now use that to populate your view model.
var vm = new ListViewModel { Wishes = wishesWithImages };
Assuming your Wish entity has a collection property of type Images
public class Wish
{
public int Id { set;get;}
public ICollection<Image> Images { set;get;}
}
public class Image
{
public int Id { set;get;}
public int WishId { set;get;}
public virtual Image Image{ set;get;}
}
As of today, Entity framework core is a light weight version of EF6 and doesn't automatically inherit all the features from EF 6. Lazy loading is not implemented yet in EF core.
Related
I have a WPF window to display details of a training class, and a list of attendees in a datagrid.
I am using Entity Framework Core and my training class model looks like this
public partial class TrainingClass
{
public TrainingClass()
{
TrainingAttendees = new HashSet<TrainingAttendee>();
}
public int ClassId { get; set; } // PK
public DateTime DateFrom { get; set; }
public DateTime DateTo { get; set; }
public string CourseName { get; set; }
public virtual ICollection<TrainingAttendee> TrainingAttendees { get; set; }
}
In my TrainingClassDetailsViewModel I have a button that opens a dialog window where the user enters in the attendee details and clicks a Save button
public void AddAttendee(object parameter)
{
TrainingAttendee attendee = new TrainingAttendee()
TrainingAttendeeViewModel vm = new TrainingAttendeeViewModel(attendee);
_windowService.ShowDialog<TrainingAttendeeEditor>(vm);
if (vm.SaveClicked)
{
_trainingClass.TrainingAttendees.Add(attendee);
}
}
This adds the new attendee so EF Core can update the database when SaveChanges is called, but this does not update the datagrid.
I'm aware I need to use ObservableCollection which uses INotifyPropertyChanged. If I implement the following and use Attendees.Add(attendee); instead, this will update the datagrid, but will not update the database.
private ObservableCollection<TrainingAttendee> _attendees;
public ObservableCollection<TrainingAttendee> Attendees
{
get => _attendees;
set
{
if (_attendees != value)
{
_attendees = value;
ApplyPropertyChange();
}
}
}
...
_attendees = new ObservableCollection<TrainingAttendee>(_trainingClass.TrainingAttendees);
The only working method I can get is to do both _trainingClass.TrainingAttendees.Add(attendee); and Attendees.Add(attendee);. But I find this odd by managing 2 lists. And I need my own ApplyPropertyChanged to be called instead.
How would I best implement this?
You can use ObservableCollection<T> as a backing store for your collection navigation properties (in place of the current HashSet<T>). Or even better, the EF Core provided ObservableHashSet<T> class. e.g.
using Microsoft.EntityFrameworkCore.ChangeTracking; // <-- required
public partial class TrainingClass
{
public TrainingClass()
{
TrainingAttendees = new ObservableHashSet<TrainingAttendee>(); // <--
}
//...
public virtual ICollection<TrainingAttendee> TrainingAttendees { get; set; }
}
For more info, see Notification entities section and the whole Change Tracking topic in the official EF Core documentation.
Also, if you are using EF Core 5.0 or later, you can utilize EF Core Change-tracking proxies which basically implement all INotifyPropertyChanging and INotifyPropertyChanged functionality for you. But you need to make all your properties virtual and also have access to DbContext and use CreateProxy extension method instead of new for creating new entity instances.
This is a good example why you should not bind directly to auto-generated (EF) model classes.
You should bind to view models or wrapper classes and convert/map between your entity objects and these.
In your AddAttendee method you wold then add the entity to the context like you are currently doing but also add a view model/wrapper to a data-bound ObservableCollection<T>:
if (vm.SaveClicked)
{
//1. add to EF context
_trainingClass.TrainingAttendees.Add(attendee);
//2. Add to source data-bound collection
vm.SourceCollection.Add(new Wrapper(attendee));
}
You'll find an example of a view model class that "wraps" a model here.
I have my entity as:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
}
I have my UserViewModel as
public class UserViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
}
I am using these as below in my controller:
//This is called from my view via ajax
public void Save(UserViewModel uv)
{
// this throws error: cannot convert from UserViewModel to Entity.User
MyRepository.UpdateUser(uv);
}
My UpdateUser in repository class is as below:
public void UpdateUser(User u)
{
var user = GetUserDetails(u.Id);
user.Name = u.Name;
user.Address = u.Address;
//using entity framework to save
_context.SaveChanges();
}
How can I correctly map UserViewModel in my controller to my entity
By using AutoMapper you can do something like:
public void Save(UserViewModel uv)
{
// this throws error: cannot convert from UserViewModel to Entity.User
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<UserViewModel , User>();
});
User u = config.CreateMapper().Map<User>(uv);
MyRepository.UpdateUser(u);
}
Or manually :
public void Save(UserViewModel uv)
{
User u = new User()
{
Id = uv.Id
Name = uv.Name;
Address = uv.Address;
};
MyRepository.UpdateUser(u);
}
Which is not good to do it manually if you change your view-model and your model then you should change your code also, but with Automapper you don't need to change the code.
EDIT1:
This is not good idea to use model-view in repository (DataAccess Core) so it would be better to keep your public void UpdateUser(User u) and don't change it, in outside it is better to pass user to UpdateUser not UserViewModel like what you have done before.
EDIT2:
In my opinion non of answered posts doesn't related to SOC (Separation on concerns) even mine...
1- When I passed UserViewModel I've violated the SOC ....
2- In the other side if I got User in Peresentation layer directly I also violated the SOC.
I think the best way is a middle layer as proxy....
Presentation <----> Proxy <----> Repository.
Your repository deals with objects of type User, so you need to map the values back to an instance of that type and then make the call.
Assuming you have a method to get the user called GetUser:
public void Save(UserViewModel uv)
{
var user = MyRepository.GetUser(uv.Id);
user.Name = uv.Name;
user.Address = uv.Address;
MyRepository.UpdateUser(user);
}
You can then save the changes in your repository class. You can attach the object to make sure there are no issues if the object was created in a different context:
public void UpdateUser(User u)
{
_context.Users.Attach(u);
_context.Entry(u).State = EntityState.Modified;
_context.SaveChanges();
}
You are doing the mapping of property values(view model->enity model) inside your repositories UpdateUser method. So use the view model class (UserViewModel) as the parameter type of that.
public void UpdateUser(UserViewModel u)
{
// Get the entity first
var user = GetUserDetails(u.Id);
// Read the property values of view model object and assign to entity object
user.Name = u.Name;
user.Address = u.Address;
//using entity framework to save
_context.SaveChanges();
}
Now from your Save method ,you can pass the view model object to this method.
This will fix your compile time error (which is your current problem in the question), but be careful about what classes you are using in what layers. If you are too much worried about using a view model class in your data access layer, you can do that in a middle service layer. But then you are getting the entity model in that layer and doing the update there.
Remember, there is no definite answer for that question. Use the approach you think is readable and consistent with the project/ team. Often times, i tend to use the term "Common DTO classes" than "View models" so i can peacefully pass those around to another layer. I keep them in a separate project (called Common DTO) and this will be cross cutting across other projects. That means i will add a reference to this Common DTO project in my Web/UI layer and my data access/service layer and use those as needed.
I am new to asp.net MVC and i have created a project using Entity Framework code first approach. I have put my POCO objects in to a separate class library called Entities.
Now i would like to get some data from my service class, which returns an Entity and output that to the View. here is some very basic code
// in POCO library
public class MyEntity() {
public int Id { get; set; }
public String Name { get; set; }
}
// in service library
public class EntityService() {
public MyEntity Get(int id) {
return new MyEntity() { Id=1, Name="This is my entity name" };
}
}
// controller in asp.net MVC web application
public MyController() : Controller
{
private EntityService _service;
public MyController(EntityService service) {
_service = service;
}
public ActionResult Index()
{
MyEntity entity = _service.Get(1);
return View(entity);
}
}
Now should i push MyEntity to the View, or should i be creating a separate ViewModel? Part of me thinks that creating a separate ViewModel would be best as to keep the separation between the Entities and my View, and also the "logic" to copy the fields i need would be in the controller. But another part of me thinks that creating a ViewModel is just going to be a near copy of the Entities so seems like a waste of time?
I would like to do it correctly, so thought i would ask here. Thanks in advance
Viewmodel is best solution.
You can put attributes(validations and other)
Your viewmodel can contain data from several data entities
As you say you get separation between the Entities and View
General approach get entities in controller and use some mapper library(I recommend emit mapper)
to map entity to your viewmodel
Given the following two models:
public class Card
{
public int CardId { get; set; }
public Media Media { get; set; }
}
public class Media
{
public int MediaId { get; set; }
[Required]
public string FileName { get; set; }
}
And the following controller method:
[HttpPost]
public ActionResult Create(Card card)
{
db.Media.Attach(card.Media);
ModelState.Remove("Media.FileName");
if (ModelState.IsValid)
{
db.Cards.Add(card);
db.SaveChanges();
}
return JsonNetSerializedResult(card);
}
I want to create a new Card, but associate it with an existing Media object. I POST to "Controller/Create" and include a "Media.MediaId" parameter that contains the ID of the existing Media record and let EF make the association.
However, after SaveChanges() is called, the only property updated on the Card instance is the CardId. I also need to retrieve the Media.FileName and set that property on the Card instance.
Is there a generic way to tell EF that I want return the updated Card data, and to also return the associated Media data commiting the data?
What may be causing some confusion is the fact that you are using your Entity Model as your ViewModel.
I would actually recommend creating real ViewModel classes that represent the information received by the Controller or used as model to the views. Not using the entity model as is. It seem like extra work, but it does give you a lot more control over what the UI manages and how the entities are modeled.
In that case you would have maybe something like a NewCardModel or just simply CardModel, that has properties for the fields displayed in the UI, as well as the id of the Media element.
Then you'd have to manually create the Card instance, populate the information (automapper can be of great help), and you would need to look up your Media instance by ID and associate that instance to the new Card instance prior to saving your changes.
This SO answer may shed some more light on the pros and cons of using ViewModels:
https://stackoverflow.com/a/10928437/1373170
I have a exceedingly simplistic data model (below). I am having trouble figuring out how I am to get my navigational properties to load from the database. I have no trouble getting them in, but the navigational property does not get set by EF it appears. I have seen several related questions, but they are slightly different or rather involved. I am looking for information on how navigational properties are treated by EF 4.2 (POCO). In the reading I've done, I got the impression that I would be able to access objects with foreign keys using navigational properties. Instead, my properties are coming back as either null or empty depending on if I instantiate my collection in the constructor.
public class AnimalDb : DbContext
{
public static AnimalDb Create(string fileName)
{
Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");
return new AnimalDb(fileName);
}
private AnimalDb(string fileName) : base(fileName) { }
public DbSet<Animal> Animals { get; set; }
}
public class Animal
{
public Animal()
{
Id = Guid.NewGuid();
Traits = new ObservableCollection<Trait>();
}
public Guid Id { get; set; }
public string Species { get; set; }
public string Name { get; set; }
public ObservableCollection<Trait> Traits { get; set; }
}
public class Trait
{
public Trait()
{
Id = Guid.NewGuid();
}
public Guid Id { get; set; }
public string Name { get; set; }
}
And here is some (simple) code that uses it:
foreach (var animal in db.Animals)
{
foreach (var trait in animal.Traits)
{
//animal.Traits count is 0, so this does not run.
//However there are traits in the database, as my populate
//function is working fine.
Console.WriteLine("{0} is {1}", animal.Name, trait.Name);
}
}
----Edit Answer Summary----
Using the article and information provided in the answers below, I was able to discover I could either eagerly load using db.Animals.Include() or enable lazy loading. There is a trick to enabling lazy loading and being able to use it though. First to enable lazy loading I added:
db.Configuration.LazyLoadingEnabled = true;
Next I changed my Traits collection in the following manner:
public virtual ObservableCollection<Trait> Traits { get; set; }
Making it virtual allows the automatically generated proxy to lazily load Traits. That's it! IMHO I think the MSDN docs should shout this load and clear in the POCO EF 4.2 coding conventions. Again thanks for the help.
There are a few reasons that your wire-up methods may appear to have no data. To load related data you need to :
explicity load the data
meet the lazy loading requirements, or
use eager loading using Include()
My guess is that you turned off the virtual proxies. There is more on the requirements here:
http://msdn.microsoft.com/en-us/library/dd456855.aspx
If you don't use lazy loading you have to explicitly tell EF to load the relation with the Include method:
foreach (var animal in db.Animals.Include(a => a.Traits))
{
foreach (var trait in animal.Traits)
{
//...
}
}
You can read more about eager loading in this article.