I'm an EF noob (any version) and my Google-foo has failed me on finding out how to do this. Which makes me think I must be doing this wrong, but here is the situation:
I'm definitely in an environment that is database first and the schema won't be updated by us coders. I'm also not a fan of 'automatic' code generation, so I've stayed away from the designer or the EF powertools (though I did run through them just to see them work).
To learn I imported the Northwind DB into my LocalDB to have something to play with while creating some simple Web API 2 endpoints. This all went well as I created slimmed down models of the Employees, Shippers, & Region tables in Northwind. Region was particularly interesting as it wasn't plural and EF had issues with that. Anyway, I got by that.
My trouble now is; I want to use a view instead of a table as my source and whatever I'm doing just doesn't seem to work. What I tried was setting it up just like I did the tables. But that produces a ModelValidationException error. I tried looking at the auto-generated code from the designer, but got no insight.
My models:
//-- employee, shipper, & region work as expected
public class employee {
public int EmployeeID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
public class shipper {
public int ShipperID { get; set; }
public string CompanyName { get; set; }
public string Phone { get; set; }
}
public class region {
public int RegionID { get; set; }
public string RegionDescription { get; set; }
}
//-- invoice is a view (actual viewname is 'Invoices')
//-- so i followed the same rules as i did for employee & shipper
//-- i have tried uppercase 'I' as well as a plural version of the model
public class invoice {
public string CustomerID { get; set; }
public string CustomerName { get; set; }
public string Salesperson { get; set; }
public int OrderID { get; set; }
public int ProductID { get; set; }
public string ProductName { get; set; }
}
My Context looks like this:
public class NorthwindDBContext : DbContext {
public DbSet<Employee> Employees { get; set; }
public DbSet<shipper> Shippers { get; set; }
public DbSet<region> Regions { get; set; }
public DbSet<Invoice> Invoices { get; set; } //-- offending line of code
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//--- fix for Region being singular instead of plural
modelBuilder.Entity<region>().ToTable("Region");
}
}
If I comment out the public DbSet<Invoice> Invoices { get; set; } line in the context everything works. Just by having the line present (even if i don't reference the Invoices property) I receive the ModelValidationException error when using the context in anyway.
Can anybody tell me what I'm doing wrong here?
Thanks.
Update: I tried this in one of my controllers, but I am too noob'ish to know if this is the right path either, though it worked as far as getting records.
using (var dbContext = new NorthwindDBContext()) {
return dbContext.Database.SqlQuery<Invoice>("select * from invoices").ToList();
}
Code-first conventions will look for an ID or InvoiceID property to use as a key. Your Invoice model has neither, while the others do. This is the specific reason your code is failing.
The less-specific one is that you can't have entities in EF which lack a unique key. If you can, have the view define a key. Otherwise, you may still be able to work around the issue.
Related
I am creating some view models for my ASP MVC web app.
I created "code first" models for database. Is it a good way to derive view models from database models?
Example database model:
public class Project
{
[Key]
public int Id { get; set; }
public int? CustomerId { get; set; }
public int TypeId { get; set; }
public string Number { get; set; }
public string Name { get; set; }
}
View model:
public class ViewModelProject : Project
{
[NotMapped]
public DateTime? Start { get; set; }
[NotMapped]
public DateTime? End { get; set; }
[NotMapped]
public string Manager { get; set; }
}
Is this the right way or is it completely false?
EDIT (subquestion):
I have some very simple database models like ProjectType, which only contains i.e. two properties. Should I also fragment those models in model view or can I make it that way:
Simple database model:
public class ProjectType
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int? Code { get; set; }
}
Can I use it like so:
public class ProjectVM
{
public string Name { get; set; }
public int Number { get; set; }
public ProjectType Type { get; set; }
}
Or does it have to be fragmented like so:
public class ProjectVM
{
public string Name { get; set; }
public int Number { get; set; }
public string Type { get; set; }
public int TypeCode { get; set; }
}
I would not recommend doing it this way. I (and many others) have tried it and it doesn't work well. You will inadvertedly run into troubles, since an MVC model has to be tailored to the view and what you get from the DB rarely fits. Sure, you can hammer it into place, but the code quickly gets messy and store-related and UI code starts to mangle together. This even shows in your example, since you have to put the NotMappedAttribute (which is related to data storage), to ViewModelProject (a class at UI level).
There are many other examples to show this problem, but an especially good one I find when you want to serialize a model object to JSON and send it to a JavaScript client. The JSON serializer takes the values of all public properties and adds them to the JSON. If you want to exclude a property, you have to mark it with a ScriptIgnoreAttribute, which you would also have to apply to the base class, which breaks separation between UI and store-related code.
The better way to go is to keep the staorage model and the MVC model separated and to map the data from one to the other (there are already pre-existing frameworks that help you with that, such as Automapper). This comes with additional advantages, for example better testability, since you are now not dependent on a specific data store to create model instances.
My problem looks simple. I need to implement a relationships between items in the database. For example: relationship between entities like computer and software shows users that computer stores a specific software and similarly - a software is installed in the specific computer. I think I should implement an entity with source id and target id or something similar. I wrote some code using code first in EntityFramework 6. Here are two classes:
public class ConfigurationItem
{
public int Id { get; set; }
public String Name { get; set; }
public String DeploymentState { get; set; }
public String IncidentState { get; set; }
[DataType(DataType.MultilineText)]
public String Description { get; set; }
[DataType(DataType.MultilineText)]
public String Note { get; set; }
public virtual ICollection<Relationship> Relationship { get; set; }
}
public class Relationship
{
[Key]
public int RelationshipId { get; set; }
[ForeignKey("ConfigurationItem")]
public int SourceId { get; set; }
[ForeignKey("ConfigurationItem")]
public int TargetId { get; set; }
public String Type { get; set; }
public virtual ConfigurationItem Source { get; set; }
public virtual ConfigurationItem Target { get; set; }
}
This solution doesn't work. I need a tip or something what should I try to make it work properly. EF throws an error about foreign key:
The ForeignKeyAttribute on property 'SourceId' on type 'cms_1.Models.Relationship' is not valid. The navigation property 'ConfigurationItem' was not found on the dependent type 'cms_1.Models.Relationship'. The Name value should be a valid navigation property name.
When I try to resolve it EF throws an error about cascade deleting. I know how to disable it but I just don't want to. I need a proper solution with that feature but I think I don't know how to do a model representing given scenario.
Simply - I need to store two foreign keys from entity "A" in the entity "B". How is it possible?
from a quick review , I can tell that you need 3 tables :
first : Computer
second : Software
third : a table , lets call it ComputerSoftware which tell which software has in what computer ( or you can also see it - which computer use what software ), which has ComputerID column and SoftwareID column.
example (source)
class Country
{
public int Id { get; set; }
public virtual ICollection<CountryCurrency> CountryCurrencies { get; set; }
}
class Currency
{
public int Id { get; set; }
}
class CountryCurrency
{
[Key, Column(Order=0)]
public virtual int CountryId { get; set; }
[Key, Column(Order=1)]
public virtual int CurrencyId { get; set; }
public virtual Country Country { get; set; }
public virtual Currency Currency { get; set; }
}
Your issue could be that in the migration file creating those tables, it will have something like
.ForeignKey("dbo.Relationship", t => t.Id, cascadeDelete: true)
This will be set on both tables, ConfigurationItem and Relationship of their Primary Key fields. When you delete one, that config tells SQL Server to delete the relationships as well and the relationship probably has a cascadeDelete: true to the parent. This will cause the cyclical cascading delete issue you are experiencing.
After the migration has been generated, go in and change one or all to cascadeDelete: false and this will fix that issue. This is what EF generates by default if I recall.
I am apparently having a real devil of a time understanding Entity Framework 6 which I am using with ASP.NET MVC 5.
The core of the matter is that I have a really quite simple data model that is typical of any real world situation where I have various business objects that have other business objects as properties (and of course they child objects may in turn have other child business objects) and also various types of lookup/type data (Country, State/Province, LanguageType, StatusType etc.) and I cannot figure out how to save/update it properly.
I keep going back and forth between two error states:
1) I either run into the situation where saving a parent business object results in unwanted duplicate values being inserted into my lookup/type tables (for example saving a business object that has been assigned an existing LanguageType of 'English' will result in another LanguageType for 'English' being inserted into the LanguageType table), or
2) I use some of the suggestions I've seen here and elsewhere on the net (e.g. Saving Entity causes duplicate insert into lookup data, Prevent Entity Framework to Insert Values for Navigational Properties ) to solve issue 1 and then find myself fighting against this same issue: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key .
I will now provide a few code snippets to help build the picture of what I am trying to do and what I am using to do it. First, an example of the entities involved:
public class Customer : BaseEntity
{
public string Name { get; set; }
[LocalizedDisplayName("Contacts")]
public virtual List Contacts { get; set; }
}
public class Contact : BaseEntity
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public int? LanguageTypeID { get; set; }
[Required]
[ForeignKey("LanguageTypeID")]
public virtual LanguageType Language { get; set; }
}
public class LanguageType : Lookup
{
[LocalizedDisplayName("CultureName")]
public string CultureName { get; set; }
}
public class Lookup : BaseEntity
{
public string DisplayName { get; set; }
public int DisplayOrder { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class BaseEntity
{
public int ID { get; set; }
public DateTime? CreatedOn { get; set; }
public DateTime? UpdatedOn { get; set; }
public DateTime? DeletedOn { get; set; }
public bool Deleted { get; set; }
public bool Active { get; set; }
public ApplicationUser CreatedByUser { get; set; }
public ApplicationUser UpdatedByUser { get; set; }
}
In my controller, I have some code like the following:
foreach(Contact contact in lstContacts)
{
customer.Contacts.Add(contact);
}
if (ModelState.IsValid)
{
repository.Add(customer);
}
Let us suppose that each of the contacts has the same LanguageType of 'English' assigned (and in this example it is the fact that I am trying to save multiple contacts that have the same LanguageType that triggers the ObjectStateManager error). Initially, the repository.Add() code just did a context.SaveChanges() which did not work as expected, so now it looks something like this (Entity variable is a Customer):
try
{
if(Entity.Contacts != null)
{
foreach(Contact contact in Entity.Contacts)
{
var entry = this.context.Entry(contact.Language);
var key = contact.Language.ID;
if (entry.State == EntityState.Detached)
{
var currentEntry = this.context.LanguageTypes.Local.SingleOrDefault(l => l.ID == key);
if (currentEntry != null)
{
var attachedEntry = this.context.Entry(currentEntry);
//attachedEntry.CurrentValues.SetValues(entityToUpdate);
attachedEntry.State = EntityState.Unchanged;
}
else
{
this.context.LanguageTypes.Attach(contact.Language);
entry.State = EntityState.Unchanged;
}
}
}
}
context.Customers.Add(Entity);
context.SaveChanges();
}
catch(Exception ex)
{
throw;
}
Is it fundamentally wrong to expect this to have worked? How am I supposed to save and example like this? I have similar problems saving similar object graphs. When I look at tutorials and examples for EF, they are all simple and they all just call SaveChanges() after doing something very similar to what I am doing here.
I've just recently been using the ORM capabilities of ColdFusion (which is hibernate under the covers) and there are would simply load the LanguageType entity, assign it to the Contact entity, save the Contact entity, assign it to the Customer and then save the Customer.
In my mind, this is the most basic of situations and I cannot believe that it has caused me so much pain - I hate to say it, but using plain old ADO.NET (or heaven forbid, ColdFusion which I really don't enjoy) would have been MUCH simpler. So I am missing SOMETHING. I apparently have a key flaw in my understanding/approach to EF and If somebody could help me to make this work as expected and help me to figure out just where my misunderstanding lies, I would greatly appreciate it. I have spend too many hours and hours on this and it is a waste of time - I have/will have countless examples just like this one in the code I am building so I need to adjust my thinking with respect to EF right now so I can be productive and do approach things in the expected way.
Your help will mean so much and I thank you for it!
Let's consider the following object graph in which a teacher instance is the root object,
Teacher --[has many]--> courses
Teacher --[Has One]--> Department
In entity framework's DbContext, each instance of an object has a State indicating whether the object is Added, Modified, Removed or Unchanged. What happens apparently is the following :
Creating the root object for the first time
In this case, in addition to the newly created root object Teacher, ALL the child objects in the graph will have the State Added as well even if they're already created. The solution for this problem is to include the foreign key property for each child element and use it instead, i.e. Teacher.DepartmentId = 3 for example.
Updating the root object and one of its child elements' properties
Suppose you fetch a teacher object from the db, and you change the Teacher.Name property as well as the Teacher.Department.Name property; in this case, only the teacher root object will have the State marked as Modified, the department's State on the other hand remains Unchanged and the modification won't be persisted into DB; Silently without any warning.
EDIT 1
I used your classes as follows and I don't have a problem with persisting the objects :
public class Customer : BaseEntity
{
public string Name { get; set; }
public virtual List<Contact> Contacts { get; set; }
}
public class Contact : BaseEntity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int? LanguageTypeID { get; set; }
public Customer Customer { get; set; }
[ForeignKey("LanguageTypeID")]
public LanguageType Language { get; set; }
}
public class LanguageType : Lookup
{
public string CultureName { get; set; }
}
public class Lookup : BaseEntity
{
public string DisplayName { get; set; }
public int DisplayOrder { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class BaseEntity
{
public int ID { get; set; }
public DateTime? CreatedOn { get; set; }
public DateTime? UpdatedOn { get; set; }
public DateTime? DeletedOn { get; set; }
public bool Deleted { get; set; }
public bool Active { get; set; }
public ApplicationUser CreatedByUser { get; set; }
public ApplicationUser UpdatedByUser { get; set; }
}
public class ApplicationUser
{
public int ID { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
}
And used the following Context :
public class Context : DbContext
{
public Context() : base("name=CS") { }
public DbSet<Customer> Customers { get; set; }
public DbSet<Contact> Contacts { get; set; }
public DbSet<LanguageType> LanguageTypes { get; set; }
public DbSet<ApplicationUser> ApplicationUsers { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//I'm generating the database using those entities you defined;
//Here we're demanding not add 's' to the end of table names
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
Then I created a unit tests class with the following :
[TestMethod]
public void TestMethod1()
{
//our context
var ctx = new Infrastructure.EF.Context();
//our language types
var languageType1 = new LanguageType { ID = 1, Name = "French" };
var languageType2 = new LanguageType { ID = 2, Name = "English" };
ctx.LanguageTypes.AddRange(new LanguageType[] { languageType1, languageType2 });
//persist our language types into db before we continue.
ctx.SaveChanges();
//now we're about to start a new unit of work
var customer = new Customer
{
ID = 1,
Name = "C1",
Contacts = new List<Contact>() //To avoid null exception
};
//notice that we're assigning the id of the language type and not
//an object.
var Contacts = new List<Contact>(new Contact[] {
new Contact{ID=1, Customer = customer, LanguageTypeID=1},
new Contact{ID=2, Customer = customer, LanguageTypeID=2}
});
customer.Contacts.AddRange(Contacts);
//adding the customer here will mark the whole object graph as 'Added'
ctx.Customers.Add(customer);
//The customer & contacts are persisted, and in the DB, the language
//types are not redundant.
ctx.SaveChanges();
}
It all worked smoothly without any problems.
As far as i know there is no build in support for reattaching modified graphs (like the SaveOrUpdate method of nHibernate). Perhaps this or this can help you.
I am essentially am trying to find a clean way to pull in data from another table. Below is a simplified version of my model. My goal is to put the platform name in the userplatform. I would like the cleanest way to do this so I assume with automapper or directly in my repository.
When I try to put a virtual reference to Platform in User Platform my code gets an error that we have a loop of cascading deletes.
Any ideas on how to resolve this problem?
public class User
{
public int UserID { get; set; }
public virtual ICollection<UserPlatform> UserPlatform { get; set; }
}
public class UserPlatform
{
public int UserPlatformID { get; set; }
public String PlatformName { get; set; }
public int UserID { get; set; }
}
public class Platform
{
public int PlatformID { get; set; }
public string Name { get; set; }
}
Alternatives
Db: Denormalize your data so that the information is stored in your user table.
Repository: Do a join inside
Repository: Do two different queries and manually build the User object.
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.