I'm trying to update a foreign key in EF6 (Code First) in an ASP.Net MVC manner.
Let me explain :
My entities
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Country Country { get; set; }
}
public class Country
{
public int Id { get; set; }
public string Name { get; set; }
}
My database
Table Countries with 2 records :
Id = 1, Name = France
Id = 2, Name = Canada
Table People with 1 record :
Id = 1, Name = Nicolas, Country_Id = 1
My code
// In a MVC application, these class has been retrieved via EF in a previous page. So now, we've lost all "proxy" informations
var p = new Person() { Id = 1, Name = "Nicolas" };
// Change country
p.Country = new Country() { Id = 2, Name = "Canada" };
// Persist all in DB
using (var db = new TestDbContext())
{
db.Persons.Attach(p); // Reattach to EF context
db.Entry<Person>(p).State = EntityState.Modified; // Flag modified state
db.SaveChanges(); // Generate only modification on field "name"
}
My issue
When the previous code is executed, the generated SQL never include the country_Id field from the person table.
My "not" issue
I know that it works perfectly when doing all these lines of codes in one EF context but in my case, I will have the data coming from my ASP.Net MVC page.
I would also like to avoid to retrieve the existing data and modify each field one by one
By retrying #ivan solution, I was first able to do what was wanted.
Here are the modifications done :
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Country Country { get; set; }
public int Country_Id { get; set; }
}
// ...
// Change country
p.Country = new Country() { Id = 2, Name = "Canada" };
p.Country_Id = 2;
But now, I get an exception when getting entities from the database.
Using this code :
// Retrieve data first
using (var db = new TestDbContext())
{
var p2 = db.Persons.First();
}
I get the following SqlException : "Invalid column name 'Country_Id1'."
Does anyone have any clues to be able to retrieve data and to update the foreign key ?
Related
This is my first dip into Entity Framework - as a long time Linq2Sql guy I never had anyone show me what EF did better. I’ve followed some MS tutorials and I have a context that creates a database and stores stuff easily. I can query it with LINQ so it’s still familiar.
What isn’t working though is a relationship. I have three classes, call them Product, Place, and Price. They all have an int Id. Product and Place both have a virtual icollection, and Price has a property each for Product and Place.
The logical model is that I have a list of products that are sold in zero or more places, and any product might have a different price in each location (think local tax adjustments).
My first test wouldn’t populate the list of prices from the product, so myProduct.Prices was null. I worked around that by getting the list of prices myself
var prices = dB.Prices.Where(...)
My problem is when I have more than one Place (Id 1 and 2), and I put in data for multiple prices, the Place is null where the Id is 2.
I have tried adding data directly to the tables (a place with Id 2, anD a price with Place_Id = 2). I have tried adding them with code.
If I go in to the Price table and change both Place_Id to 1, it works in that they both retrieve Place 1. If I set them both to two they both give me null Place. If I set one each way, the one with 1 works and the other is null.
So my relationship works but only when the FK is 1. WTF have I done?
edit: code samples (sorry, I was on my phone earlier)
{ //declared in my dbcontext
...
public DbSet<Product> Products { get; set; }
public DbSet<Place> Places{ get; set; }
public DbSet<Price> Prices{ get; set; }
...
}
//Just to be clear, below are separate from the dbcontext
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
...
[InverseProperty("Product")]
public ICollection<Price> Prices { get; set; }
}
public class Place
{
public int Id { get; set; }
public string Name { get; set; }
...
[InverseProperty("Place")]
public ICollection<Price> Prices { get; set; }
}
public class Price{
public int Id { get; set; }
public Product Product { get; set; }
public Place Place { get; set; }
...
}
I tried inserting rows directly, also I tried
//In the Seed(Context) method of my DbContextInitializer, where db is an instance of my dbcontext
var a1 = new Place { Name = "Place 1"};
var a2 = new Place { Name = "Place 2"};
var p = new Product { Name = "Test Product" };
db.Products.Add(p);
db.Associations.Add(a1);
db.Associations.Add(a2);
db.Prices.Add(new Price { Amount = 10.1m, Place= a1, Product = p });
db.Prices.Add(new Price { Amount = 3.45m, Place= a2, Product = p });
db.SaveChanges();
All of that data gets inserted and I can interrogate the database to see it.
The code that is coming unstuck is:
foreach (var p in db.Products.ToList()) //NB if I try this without the ToList I get an exception about an open data reader
{
var price = 0m;
foreach (var o in db.Prices)
{
//there are two price records which we created above.
//In the first Price, o = {Id = 1, Place_Id = 1, Product_Id = 1}. This works.
//In the second Price, o = {Id = 2, Place_Id = 2, Product_Id = 1}. o.Place is null
// Yes, there is absolutely a Place {Name = "Place 2", Id = 2} in the database
if (o.Place.Id == ...)price += o.Amount;
}
...
}
Something interesting I noticed. If I jam in more products, it works with any product Id. Also (and I suspect this is the underlying issue), I notice that o.Product is of type Product, however o.Place is of type DynamicProxies.Place_Guid - but I'm not understanding why these are different I have declared the properties in identical fashion as you can see above.
I am trying to learn about Entity Framework 6, and I am running into an issue, that I have been able to reproduce in a test project:
A Movie has a Nameand a Revenue. A Revenue has a GrossIncome:
public class Movie
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public Revenue Revenue { get; set; }
}
public class Revenue
{
[Key]
public int Id { get; set; }
public double GrossIncome { get; set; }
}
I am trying to use EF6 code-first to persist some data about movies in the database:
public class MovieContext: DbContext
{
public MovieContext(): base("name=testDB") { }
public DbSet<Movie> Movies { get; set; }
public DbSet<Revenue> Revenues { get; set; }
}
I start by inserting a new movie, with its associated revenue in the DB:
using (var context = new MovieContext())
{
Revenue revenue = new Revenue()
{
GrossIncome = 10
};
Movie movie = new Movie()
{
Name = "foo",
Revenue = revenue
};
context.Movies.Add(movie);
context.SaveChanges();
}
I can see in SQL Server that the tables are created, and that a Movies.Revenue_Id column has been created, with a foreign key relationship to Revenue.Id.
If I try to query it using SQL, it works fine:
SELECT Movies.Name, Revenues.GrossIncome
FROM Movies
LEFT JOIN Revenues ON Movies.Revenue_Id = Revenues.Id
returns
Name GrossIncome
----------------------
foo 10
However, if I try to use Entity Framework to query the data:
using (var context = new MovieContext())
{
List<Movie> movieList = context.Movies.ToList();
Console.WriteLine("Movie Name: " + movieList[0].Name);
if (movieList[0].Revenue == null)
{
Console.WriteLine("Revenue is null!");
}
else
{
Console.WriteLine(movieList[0].Revenue.GrossIncome);
}
Console.ReadLine();
}
The console reads:
Movie Name: foo <- It shows that the query works, and that the data in the main table is fetched.
Revenue is null! <- Even though the data in the DB is correct, EF does not read the data from the foreign key.
My question is simple: what am I doing wrong? How are the foreign key values supposed to be read?
Just include the child entity you want to load:
using (var context = new MovieContext())
{
List<Movie> movieList = context.Movies
.Include(m => m.Revenue) // ADD THIS INCLUDE
.ToList();
Console.WriteLine("Movie Name: " + movieList[0].Name);
if (movieList[0].Revenue == null)
{
Console.WriteLine("Revenue is null!");
}
else
{
Console.WriteLine(movieList[0].Revenue.GrossIncome);
}
Console.ReadLine();
}
This will load the movies - and also make sure that all the references to their respective .Revenue references have been loaded, too.
I would like to know if there is a way to add two linked objects to a database through entity framework before the linked field has been generated by the database.
I am still learning EF and I'm not exactly sure how to ask this question clearly so here is an example of what I am trying to achieve:
I have two classes:
class Sale
{
public int Id { get; set; } // generated by SQL Server
public string Foo { get; set; }
public string Bar { get; set; }
public virtual ICollection<SalesComment> Comments { get; set; }
}
class SalesComment
{
public int Id { get; set; }
public int SaleId { get; set; }
public string Comment {get; set; }
}
Using fluent api in my 'dbcontext' class I link the two objects like so:
modelBuilder.Entity<Sale>().HasMany(s => s.Comments).WithRequired().HasForeignKey(c => c.SaleId);
This works great, I can retrieve a Sale object from the database and I can loop through the comments linked to it by SaleId.
What I want to do is when creating a new Sale, add a Comment as I am creating it, with EF realising this is happening and adding the SaleId to the Comments table once it is generated by the database, something like:
using (MyDatabase db = new MyDatabase())
{
var sale = db.Set<Sale>();
sale.Add(new Sale
{
Foo = "Test",
Bar = "Test",
Comment.Add(new SalesComment .... //this is the line i'm not sure about
});
db.SaveChanges();
}
Is something like this possible? Or would I have to first save the Sale object to the database so that a SaleId is generated, then retrieve that object again so I can read the SaleId to use for a new SalesComment.
Try this.
using (MyDatabase db = new MyDatabase())
{
var sale = db.Set<Sale>();
var saleComment = new SalesComment{Comment="Hello"};
var saleToAdd = new Sale
{
Foo = "Test",
Bar = "Test",
Comments = new List<SalesComment> {saleComment}
});
sale.Add(saleToAdd);
db.SaveChanges();
// After saving changes, these 2 values will be populated and are equal
var saleID = saleToAdd.Id;
var saleCommentSaleId = saleComment.saleId;
}
You don't have to retrieve the object again if you cache it properly before adding it to the DbSet.
Not quiet sure how to word the title for this one so feel free to edit if it isn't accurate.
Using an example, what I am trying to accomplish is update a record in table foo, and then create new records in a subsequent table that has the foo tables PK as foreign key, think One-to-Many relationship.
How do I update a table that has foreign key constraints and create a new related record(s) in these subsequent table(s)?
Currently I am using Entity Framework 6 to .Add and .Attach entities to the context and save them to the database.
Edit
To clarify further what I am trying to achieve, the below object is a cut down made up example what I am trying to save to the context. If I try to .Add intObj after "Billy Bob" has already been created because he has bought a new car, another service, or his tyres have changed it will create a new Billy Bob record (duplicate) and the corresponding related tables.
intObj.FirstName = "Billy";
intObj.Lastname = "Bob";
intObj.Important = 100;
intObj.LastSeen = DateTime.Now.Date;
intObj.Cars = new List<Car>{
new Car{
Model = "Commodore",
Make = "Holden",
YearMade = DateTime.Today.Date,
Odometer = 15000,
EmailWordCount = 500,
TyreStatuss = new List<TyreStatus>{
new TyreStatus{
Tyre1 = "Good",
Tyre2 = "Good",
Tyre3 = "Okay",
Tyre4 = "Okay"
}
},
Services = new List<Service>{
new Service{
Cost = "$500",
Time = "2 Days",
Date = DateTime.Today
}
},
}
};
Thanks
In the following snippets you have Employee class, which references two more entities: a collection of Assignment and a single Country.
ORMs like EF , NHibernate, etc... have a feature known as Transitive Persistence, that is, if an object (Assignment and Country) is referenced by a persistent one (Employee), then Assignments and Country will eventually become persistent too when, in your EF case, SaveChanges method gets invoked in the Context, without you explicitly save them.
public class Employee
{
public virtual Guid Id { get; protected set; }
public virtual string EmployeeNumber { get; set; }
public virtual Country BirthCountry { get; set; }
private ICollection<Assignment> _assignment = new List<Assignment>();
public virtual ICollection<Assignment> Assignments
{
get
{
return _assignment;
}
set
{
_assignment= value;
}
}
}
public class Assignment
{
public virtual Guid Id { get; protected set; }
public virtual DateTime BeginTime { get; set; }
public virtual DateTime EndTime { get; set; }
public virtual string Description{ get; set; }
}
public class Country
{
public virtual Guid Id { get; protected set; }
public virtual string Name { get; set; }
}
//Somewhere in your program
private void SaveAllChanges()
{
_db = new EFContext();
//Creating a new employee here, but it can be one loaded from db
var emp = new Employee { FirstName = "Emp Name",
LastName = "Emp Last", EmployeeNumber = "XO1500"
};
emp.BirthCountry = new Country { Name = "Country1" };
emp.Assignment.Add(new Assignment{ BeginTime = DateTime.Now,EndTime=DateTime.Now.AddHours(1) });
//Only employee is explicitly added to the context
_db.Employees.Add(emp);
//All the objects in the employee graph will be saved (inserted in this case) in the db.
_db.SaveChanges();
}
}
EDIT:
That is very similar to my code above, once "Billy Bob" is created you only need to update it, and that include any new service he buy;
Pseudo code:
var bob = _db.Clients.SingleOrDefault(c=> c.Id = "Bob Row Id")
//Bob buy a car:
bob.Cars.Add(new Car()...)
//...and change tire 1 from an old car
var car = bob.Cars.SingleOrDefault(c=> c.Id = "Car Row Id")
car.TireStatus.Tire1 = "New"
....
//Persist all changes
//Existing objects will be updated..., and the new ones created in this process will be inserted
_db.SaveChanges()
Let me know if this clarify your ideas
I have 2 classes which hold data for restaurants.
Status.cs
public class Status
{
[Required]
public int StatusId { get; set; }
[Required]
[DisplayName("Status")]
public string Name { get; set; }
}
Restaurant.cs
public class Restaurant
{
public int RestaurantId { get; set; }
[Required]
public string Name { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
public string Telephone { get; set; }
[Required]
public int StatusId { get; set; }
// NAVIGATION PROPERTIES
public virtual Status Status { get; set; }
}
I am trying to seed data into the database.
First, I seed the status table, then I wish to seed the Restaurants table.
var statuses = new List<Status>
{
new Status { Name = "Draft" },
new Status { Name = "Live" },
new Status { Name = "Invisible" },
new Status { Name = "Discontinued" }
};
statuses.ForEach(a => context.Statuses.Add(a));
var restaurants = new List<Restaurant>
{
new Restaurant { Name = "The Restaurant Name", Email = "email#restaurant.com", Telephone = "012345566787", StatusId = 1 }
};
restaurants.ForEach(a=>context.Restaurants.Add(a));
base.seed(context);
This doesn't work, because it doesn't like the way I try to seed StatusId = 1 into Restaurant.
I know that I can create a new Status within the new Restaurant, however, I have already seeded the statuses into the database. How do I go about setting the Restaurant's Status to Draft???
Do I have to do this every time???
new Restaurant { Name = "The Restaurant Name", Email = "email#restaurant.com", Telephone = "012345566787", StatusId = new Status { Name = "Draft"} }
Will this not literally generate a new row in the status table called Status every time I create a new Restaurant with the status "Draft"?
Don't set the StatusId, set the Status:
var restaurants = new List<Restaurant>
{
new Restaurant {
Name = "The Restaurant Name",
Email = "email#restaurant.com",
Telephone = "012345566787",
Status = statuses.Single(s=>s.Name=="Draft") }
};
That way the StatusID is set by the context and uses the same ID as the Status.
If you want to reference the Status in the list by Index (e.g. statuses[0] for "Draft") that works too; selecting by name is more appropriate, though, IMHO.
EDIT
Just read the end of your question - so to answer that:
When you create a "new" Status and attach it to your Restaurant, the context doesn't know that you want to use the existing status of "Draft", so it does what you tell it - creates a new one. When you attach an existing Status from the context is used that ID since it's the same instance.