I find myself repeating the following pattern when a user is required to link two entities together and it feels wrong.
I have two entities, Site and User say, a user msut have a site and a site can have many users. On the add user form the available Sites are displayed as a DropDown List.
Example Code:
public class Site()
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<User> Users { get; set; }
}
public class User()
{
public int Id { get; set; }
public string Name { get; set; }
public int SiteId { get; set; }
public Site Site { get; set; }
}
public class UserViewModel()
{
public User User { get; set; }
public List<Site> AvailableSites { get; set; }
}
Add User View
#model SomeNameSpace.UserViewModel
#using (Html.BeginForm())
{
<div>
<div>#Html.LabelFor(m=>m.User.Site)</div>
<div>#Html.DropDownListFor(m=>m.User.Site.Id,
new SelectList(Model.AvailiableSites,"Id","Name"))
</div>
</div>
<--- the rest of the view --- >
Controller
public ActionResult Add()
{
UserViewModel u = new UserViewModel();
u.AvailableSites = context.Sites.ToList();
return View(u);
}
[HttpPost]
public ActionResult Add(UserViewModel model)
{
//THIS FEELS WRONG
Site s = context.Sites.Where(s=>s.Id == model.User.Site,Id).FirstOrDefault();
model.User.Site = s;
context.Users.Add(model.User);
context.SaveChanges();
}
It seems odd to be looking up the site in the controller, I expected the dropdown to link the two entities together - it displays the site name correctly - but it only sets the UserViewModel.User.Site.Id value.
Am I missing something, or is this the preferred/correct way?
You don't need to grab the whole entity in order to link it. As long as the FK has been properly set up, all you would need to do is make sure that the FK's ID property has been set (which it looks like you have, considering that you're using model.User.SiteId to grab the entity).
EF doesn't need to know the full entity in order to link them. It just needs to know its ID. Also, sometimes it can be bad to assign the full entity. Depending on how you do it you could create duplicates.
Related
I generated Entity Model from my database in my MVC5 App.
When I try to add [DispalyName] to some properties it works fine, but after some time app refreshes this class by itself and removes all my custom code
public partial class Patient
{
public Patient()
{
this.PatientDetails = new HashSet<PatientDetail>();
}
public int PatientID { get; set; }
[DisplayName("Last Name")]
public string LastName { get; set; }
public string FirstName { get; set; }
public virtual ICollection<PatientDetail> PatientDetails { get; set; }
}
Why MVC does it and how to disable that?
I believe since you're using Database first, the entities will get completely re-created every time you refresh, thus you lose your custom attributes.
Also, to go off of Joe's comment, you should make a view model and put your [Display] attributes there and not directly on the entity.
I'm learning EF Code First and am having trouble when updating existing records. I've boiled it down to this simple example:
This works:
using(var db = new DataContext()){
var p = db.People.Find(1);
p.Name="New Name";
Console.WriteLine(p.Gender.Name); //<--Unnecessary property access
db.SaveChanges(); //Success
}
...but this fails (when the WriteLine is removed):
using(var db = new DataContext()){
var p = db.People.Find(1);
p.Name="New Name";
db.SaveChanges(); //DbValidationError "Gender field is required."
}
Why do I have to access/load the Gender propery if I'm not using it and the data is already correctly stored in the database? I just want to change the Name on an existing record. In this example, Gender is a one-to-many association stored as Gender_Id in the People table. The classes are defined like this:
public class Person
{
[Key]
public int PersonId { get; set; }
[Required, MaxLength(50)]
public string Name { get; set; }
[Required, Column("Gender")]
virtual public GenderCode Gender { get; set; }
}
public class GenderCode
{
[Key]
public int Id { get; set; }
[Required, MaxLength(10)]
public string Name { get; set; }
}
public class DataContext:DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<GenderCode> GenderCodes { get; set; }
}
Of course, the fully defined classes are to have many more fields. I'd rather not have to access every dependant property every time I want to modify an unrelated value.
Is there a way to load an object, change a field, and save it without loading all related objects first?
Yes, this is necessary because of some horrible design mistakes in EF.
Check out my similar question, EF: Validation failing on update when using lazy-loaded, required properties
One trick is declaring FK properties along with the OO relations:
[ForeignKey("GenderId"), Column("Gender")]
virtual public GenderCode Gender { get; set; }
[Required]
public int GenderId { get; set; }
It is because you are using data annotations and Required attribute has also meaning for validation. Once you set navigation property as Required by data annotation it must be filled / loaded when you are going to persist entity to the database.
Based on great info out here...I've got my edit and create VM working great. My VM contains "SelectList" collections and the DD look like this.
#Html.DropDownListFor(model => model.softwaremanufacturerid, Model.ListOfManufacturers)
My question has to do with the Details action. Is there a way I can use the list I've already built (the lists are in an ancestor VM class that all the concrete ones inherit) to display the Manufacturers name instead of the FK? What I get now is just the FK.
Thanks
The list values are never POSTed. Only the selected value. So in the corresponding action to which you are submitting this form you could use this value to fetch back the list from the database if you ever needed to redisplay the same view.
You might be able to do something like this:
#Html.DisplayFor(model => model.SoftwareManufacturer.Name)
This assumes that you have classes which look like this:
public class Product
{
[Key]
public Guid Id { get; set; }
public String Name { get; set; }
[ForeignKey("SoftwareManufacturer")]
public Guid SoftwareManufacturerId { get; set; }
public virtual Manufacturer SoftwareManufacturer { get; set; }
}
public class Manufacturer
{
[Key]
public Guid Id { get; set; }
public String Name { get; set; }
}
I think that will do what you want.
I really need someone to help me to fully understand how to do many-to-many relationship with Entity Framework 4 CTP 5, POCO. I need to understand 3 concepts:
How to config my model to indicates
some tables are many-to-many.
How to properly do insert.
How to properly do update.
Here are my current models:
public class MusicSheet
{
[Key]
public int ID { get; set; }
public string Title { get; set; }
public string Key { get; set; }
public virtual ICollection<Author> Authors { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
public class Author
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
public string Bio { get; set; }
public virtual ICollection<MusicSheet> MusicSheets { get; set; }
}
public class Tag
{
[Key]
public int ID { get; set; }
public string TagName { get; set; }
public virtual ICollection<MusicSheet> MusicSheets { get; set; }
}
As you can see, the MusicSheet can have many Authors or Tags, and an Author or Tag can have multiple MusicSheets.
Again, my questions are:
What to do on the
EntityTypeConfiguration to set the
relationship between them as well as
mapping to an table/object that
associates with the many-to-many
relationship.
How to insert a new music sheets
(where it might have multiple
authors or multiple tags).
How to update a music sheet. For
example, I might set TagA,
TagB to MusicSheet1, but later I need to change the tags to TagA
and TagC. It seems like I need
to first check to see if the tags
already exists, if not, insert the
new tag and then associate it with
the music sheet (so that I doesn't
re-insert TagA?). Or this is
something already handled by the
framework?
Thank you very much. I really hope to fully understand it rather than just doing it without fully understand what's going on. Especially on #3.
In the EF4 CTP5 the relationship is done by default convention when you put public virtual ICollection in each of the classes of the many to many relationship, as you already have done, your context class should look like this:
public class YourContextName : DbContext
{
public DbSet<MusicSheet> MusicSheets { get; set; }
public DbSet<Tag> Tags { get; set; }
public DbSet<Author> Authors { get; set; }
}
Very simple you just create a instance of the MusicSheet class and then add all the instances of you authors and tags to each of the collections of Authors and Tags in your MusicSheet, and then add your instance of MusicSheet to your context collection of MusicSheets and then call SaveChanges:
MusicSheet musicSheet = new MusicSheet
{
Title = "Music Sheet 1",
Key = "Key",
Authors = new List<Author>
{
new Author
{
Name = "Author 1",
Bio = "Author 1 biographic text..."
},
new Author
{
Name = "Author 2",
Bio = "Author 2 biographic text..."
}
},
Tags = new List<Tag>
{
new Tag {TagName = "TagA"},
new Tag {TagName = "TagC"}
}
};
var context = new YourContextName();
context.MusicSheets.Add(musicSheet);
context.SaveChanges();
To update you have to load your MusicSheet and remove the tags you don't want and then add the ones you need to add, this is how:
var context = new YourContextName();
var myMusicSheet = context.MusicSheets.First();
//The Tag you wnat to remove.
var tagToRemove = myMusicSheet.Tags.First();
var tagToAdd = new Tag {TagName = "TagX"};
myMusicSheet.Tags.Remove(tagToRemove);
myMusicSheet.Tags.Add(tagToAdd);
context.Entry(myMusicSheet).State = EntityState.Modified;
context.SaveChanges();
You can also find any author and/or tag that you know that exist and added to your MusicSheet and vice versa, but this is the foundation.
Remember this is for the EF4 CTP5 Code first...
Excuse me my English is not my main language, I hope this can help you, best regards from Dominican Republic.
PS: Don't forget to add references to EntityFramework and System.Data.Entity, is your responsibility to do anything else like unit test, validation, exception handling...etc
EDIT:
First you need to add a constructor to your models:
public class Tag
{
[Key]
public int ID { get; set; }
public string TagName { get; set; }
public Tag()
{
MusicSheets = new List<MusicSheet>();
}
public virtual ICollection<MusicSheet> MusicSheets { get; set; }
}
...Then you can do something like this:
var context = new YourContextName();
var newMusicSheet = new MusicSheet();
newMusicSheet.Title = "Newly added Music Sheet";
//Your existing Tag.
var existingTag = contex.Tags.Find(3);
existingTag.MusicSheets.Add(existingTag);
context.Entry(existingTag).State = EntityState.Modified;
context.SaveChanges();
You can do the same for all your models.
I hope this can help you!
You do not really need an EntityTypeConfiguration to set the relationship between them. It should work as it is right now. With CTP5 all you have to do to establish a many-to-many relationship is to include ICollection in both entities.
Now about how to perform inserts and deletes, there are two ways I know of. The one I usually use is create an entity for the resultant table of the many-to-many relationship, then create an instance of this entity and feed it with all the data that is required, including instances of the other entities (the ones that have the many-to-many relationship). And finally I simply add it to the repository and commit the transaction (usually using a UnitOfWork class).
Quick example:
public class Item
{
public int ID { get; set; }
public string Title { get; set; }
public virtual ICollection<Bid> Bids { get; set; }
}
public class User
{
public int ID { get; set; }
public string Username{ get; set; }
public string Email { get; set; }
public string Password { get; set; }
public virtual ICollection<Bid> Bids { get; set; }
}
public class Bid
{
public int ID { get; set; }
public float Amount { get; set; }
public DateTime Date { get; set; }
public virtual Item Item { get; set; }
public virtual User User { get; set; }
}
Then I would simply create instances of the Bid entity.
public void PlaceBid(User user, Item item, int amount)
{
if (ValidateBid(amount, user, item))
{
Bid bid = new Bid
{
Amount = amount,
Date = DateTime.Now,
User = user,
Item = item
};
try
{
repository.Add(bid);
unitOfWork.Commit();
}
catch (Exception ex)
{
//TODO: Log the exception
throw;
}
}
}
I have (for example) these two objects:
class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
...
public List<Order> Orders;
}
class Order
{
public int Id { get; set; }
public string Description { get; set; }
...
}
I want one View on which you can edit the Customer data
(that part is already working fine), but I want some kind of
'dynamic grid' to add/remove/update orders on the same page.
How can you do this? With jQuery you can add or remove html controls, but to add or remove
an Order object with it?
Any ideas?
thanks,
Filip
I would recommend you checking out the following blog post from Steve Sanderson which illustrates a nice technique which could be used for editing a variable length list in ASP.NET MVC 2.