MVC3 Details Action Foreign Key Name Field - entity-framework

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.

Related

One to many relationship: why does ID field go in other entity?

In Entity Framework when I want to specify that an entity has many of another type of entity it seems to do things backwards to me.
For instance let's say I have a keyword entity that is used in several places throughout my app. All keywords are unique but on my other entities I want to have multiple keywords attached them so to me this would make sense:
class Page
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Keyword> Keywords { get; set; }
}
class Search
{
public int ID { get; set; }
public DateTime Date { get; set; }
public virtual ICollection<Keyword> Keywords { get; set; }
}
class Keyword
{
public int ID { get; set; }
public string Name { get; set; }
}
However when I do this the foreign key is added to the Keyword table whereas I want it to be on the actual entity so I can look at it in database and see small list of keywords instead of looking at keyword and seeing a ridiculous large number of page results.
So instead to get Entity Framework to put the Keyword_IDs on Page and Search entities I am doing this:
class Page
{
public int ID { get; set; }
public string Name { get; set; }
}
class Search
{
public int ID { get; set; }
public DateTime Date { get; set; }
}
class Keyword
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Page> Pages { get; set; }
public virtual ICollection<Search> Searches { get; set; }
}
This feels backwards as I am specifying the relationship on the entity that doesn't get the foreign ID field in the database table.
I feel like I am doing something wrong as I should be able to see the relationship by looking at my search & page class.
I am sorry for the basic question but for some reason I have read documentation and I am not fully understanding it.
In a one-to-many association it's always the many side that refers to the one side. How else would you implement it? If a Page would have a KeywordId as FK, it could only have one keyword, ever.
Also, even when a Keyword would belong to a myriad of pages, that doesn't mean you always have to access all of these pages through one keyword. You'd only do that if you'd do a search for pages in which specific keywords are used.
But now back to your model. You can't have one-to-many associations here. It would mean that any keyword can only belong to one Page or one Search. And if you invert the relationship, as you proposed, a Page or Search can only ever have one keyword (the one that Keyword_ID refers to).
In reality, you're dealing with many-to-many associations and the good news is, it leaves your Keyword intact.
Modelling it as many-to-many doesn't change the way your model looks (the first version), but the mapping is different:
modelBuilder.Entity<Page>().HasMany(p => p.Keywords)
.WithMany()
.Map(m =>
{
m.ToTable("PageKeyword");
m.MapLeftKey("PageID");
m.MapRightKey("KeywordID");
});
modelBuilder.Entity<Search>().HasMany(s => s.Keywords)
.WithMany()
.Map(m =>
{
m.ToTable("SearchKeyword");
m.MapLeftKey("SearchID");
m.MapRightKey("KeywordID");
});
This will generate two junction tables in your database, PageKeyword and SearchKeyword that record the many-to-many associations.

Entity Framework Navigation Woes

I have Business and BusinessProgram declared as:
public class Business : DbIdEntity
{
public string Name { get; set; }
public virtual Address PhysicalAddress { get; set; }
public virtual Address PostalAddress { get; set; }
public Guid OwnerKey { get; set; }
public virtual Account Owner { get; set; }
public virtual IEnumerable<BusinessProgram> BusinessPrograms { get; set; }
}
public class BusinessProgram : DbEntity<Guid>
{
public Business Business { get; set; }
public ProgramType ProgramType { get; set; }
public DateTime? EffectiveDate { get; set; }
public DateTime? ExpireDate { get; set; }
}
DbIdEntity and DbEntity are just base classes where the primary key (and an autonumbering Id field are declared.
When I query it using this query
foreach (Data.Business business in context.Businesses.Include(b => b.Owner)
.Include(b => b.PhysicalAddress)
.Include(b => b.Owner)
.Include(b => b.BusinessPrograms)
.OrderBy(b => b.Name))
I'm also using a convention that makes properties ending in "Key" the primary and foreign keys instead of the default "Id".
I get the error:
"A specified Include path is not valid. The EntityType
'Data.Business' does not declare a navigation
property with the name 'BusinessPrograms'."
What am I doing wrong?
UPDATE
I used IEnumerable instead of ICollection. Using the correct navigation property type fixed the issue.
I used IEnumerable for my navigation type instead of ICollection. Changing it to ICollection fixed the issue.
I've had rather limited success with using base classes in the way you describe. I'd try "flattening" your model first, and getting it working like that. Then you could try re-introducing the base classes; you might be able to get that working too.
Here it looks as though BusinessProgram should contain a FK property called BusinessProgram_BusinessId if you want to use the default convention. Alternatively, you could give it a different name and use an attribute to override the default convention:
[ForeignKey("Business")]
public int BusinessId { get; set;}

DBContext simplest way to update foreign key

var orgAcc = db_.Accounts.Find(account.Id);
db_.Entry(orgAcc).CurrentValues.SetValues(account);
orgAcc.Company = db_.Companys.Find(account.Company.Id);
db_.SaveChanges();
Is this the simplest way to update an entity's association ?
public class ChartofAccount: MasterData
{
[Key]
public int Id { get; set; }
[Required]
[StringLength(6)]
public string Code { get; set; }
public virtual Company Company { get; set; }
[Required]
public string AccountName { get; set; }
[Required]
[StringLength(3)]
public string AccountCurrency { get; set; }
public virtual AccountCatagory Category1 { get; set; }
public virtual AccountCatagory Category2 { get; set; }
public string Reference { get; set; }
public bool HasTransaction { get; set; }
}
The way SetValues works is to do a property-by-property compare, and for each property from the left-hand object that is also in the argument, that has a matching type, it will update the left-hand object with the value from the argument.
I presume account.Company is a different type of object to orgAcc.Company, such as something that has come in from an MVC controller argument (ie account and it's referenced objects are not EF entities). In this case your approach seems a sound way of doing it.
That being said, orgAcc probably has a Company property, and a CompanyId property, in order to support the EF relationships, so, if your account object followed the same pattern, ie storing a CompanyId field directly, rather than having to navigate through the company, then SetValues could automatically update the CompanyId field, which should update the foreign key when you save changes. This way you could also avoid the step that specifically assigns the orgAcc.Company field.

Do all associated objects have to be accessed (lazyloaded) before an existing object can be saved?

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.

EF CTP4 Missing columns in generated table

I'm having an issue that i just can't seem to figure out. Lets say I have 2 Entities defined in my domain; Person and Document. Below is the definition for Document :
public class Document
{
public int Id { get; set; }
[Required]
[StringLength(255)]
public string Title { get; set; }
public DateTime Date { get; set; }
public virtual Person Owner{ get; set; }
public virtual Person AssignedTo { get; set; }
}
Now, when EF CTP4 creates the SQL table on initialize, there is only one field mapping to a Person.Id being Owner_id. Whatever i try, the field for AssignedTo is never created.
Anything that could solve this?
Regards,
avsomeren
Your code perfectly created the desired schema in the database for me:
If you don't get this schema in you DB then my guess is that something is not right with the rest of your object model. Could you post your full object model please?
Another Solution:
While your current Document class will give you the desired results, but you can still take advantage of the Conventions for Code First and explicitly specify the FKs for your navigation properties:
public class Document
{
public int Id { get; set; }
[Required][StringLength(255)]
public string Title { get; set; }
public DateTime Date { get; set; }
public int OwnerID { get; set; }
public int AssignedToID { get; set; }
public virtual Person Owner { get; set; }
public virtual Person AssignedTo { get; set; }
}
Code First will now infer that any property named <navigation property name><primary key property name> (e.g. OwnerID), with the same data type as the primary key (int), represents a foreign key for the relationship.
This essentially results to the same DB schema plus you have the FKs on your Document object as well as navigation properties which gives you ultimate flexibility to work with your model.