In my application I want to assign movie with director. Of course there is no point to store duplicated directors, so somehow I need to prevent adding duplicated rows.
I remember old days having fun with sql server and that did the trick:
1. Get Director Id
1a. If director exist return existing director Id
1b. If director not exist add new and return added director Id
2. Add new Movie with directorId assigned.
I am just learning Entity Framework and I cant do this in that way. I have to change the way I look at my database as collections. So I am adding Director to the Movie. I am not sure if I explained that correctly, so I think it would be better to show code:
class Program {
static void Main(string[] args) {
Movie firstMovie = new Movie() { Name = "Titanic" };
Movie secondMovie = new Movie() { Name = "Pulp Fiction" };
Movie thirdMovie = new Movie() { Name = "Matrix" };
Director director = new Director() { Name = "Warner Bros" };
firstMovie.Director = director;
secondMovie.Director = director;
thirdMovie.Director = director;
Db.AddMovie(firstMovie);
Db.AddMovie(secondMovie);
Db.AddMovie(thirdMovie);
}
}
public class Movie {
public int Id { get; set; }
public string Name { get; set; }
public int DirectorId { get; set; }
[ForeignKey("DirectorId")]
public virtual Director Director { get; set; }
}
public class Director {
public int Id { get; set; }
public string Name { get; set; }
public virtual List<Movie> Movies { get; set; }
public Director() {
Movies = new List<Movie>();
}
}
public class Db {
public static int AddMovie(Movie movie) {
using (var context = new MovieContext()) {
context.Movies.Add(movie);
context.SaveChanges();
return movie.Id;
}
}
}
public class MovieContext : DbContext {
public DbSet<Movie> Movies { get; set; }
public DbSet<Director> Directories { get; set; }
}
And the thing is after that I have 3x the same Director in my database. I expected to have only 1. But how can I archieve that? How can I prevent adding the same Directories? I mean I cant check if there is already directory with the same name, because I am not adding them manually. Entity Framework is adding new Directory, not me. So, what can I do with it?
What I've manged is to add directorId and let entity framework assign with proper object. This is how I've changed main:
static void Main(string[] args) {
Movie firstMovie = new Movie() { Name = "Titanic" };
Movie secondMovie = new Movie() { Name = "Pulp Fiction" };
Movie thirdMovie = new Movie() { Name = "Matrix" };
Director director = Db.GetDirector("Warner Bros");
firstMovie.DirectorId = director.Id;
secondMovie.DirectorId = director.Id;
thirdMovie.DirectorId = director.Id;
Db.AddMovie(firstMovie);
Db.AddMovie(secondMovie);
Db.AddMovie(thirdMovie);
}
And that's my new method which will return Director (and add if it is necessary)
public static Director GetDirector(string directorName) {
using (var context = new MovieContext()) {
if (context.Directories.Any(x => x.Name == directorName))
return context.Directories.FirstOrDefault(x => x.Name == directorName);
else {
Director newDirector = new Director() { Name = directorName };
context.Directories.Add(newDirector);
context.SaveChanges();
return newDirector;
}
}
}
So it is the best way to do it? I am not sure if adding new Director is proper coded. Do you have any tips?
You could go the way from the other side.
Just add the Movie objects to your one Director object's collection. After that save the Director object to database and it should work like you want.
Only as sample code:
Movie firstMovie = new Movie { Name = "Titanic" };
Movie secondMovie = new Movie { Name = "Pulp Fiction" };
Movie thirdMovie = new Movie { Name = "Matrix" };
// here you should take one from data context or create it, if it does not exist
Director director = new Director { Name = "Warner Bros" };
director.Movies.Add(firstMovie);
director.Movies.Add(secondMovie);
director.Movies.Add(thirdMovie);
// add new or update director with new movies
Db.AddOrUpdateDirector(director);
And of course, instead of just create a new one you should check if the directory already exist in datacontext and use that one.
Unfortunately, the way you're doing it will always result in a new director row for each reference to the same director object. Why? Because they are being added in three different contexts.
EF only tracks objects through DbContext. It will (sadly, maybe) not attempt to glean anything about the state of an object within a graph from properties of that object. So, the second time you save a Movie with the same director as the first, EF simply sees that it doesn't know anything about that Director and proceeds to add it to the graph as a new object as well. When SaveChanges() is called, it is duly added to the database.
You can watch this happen by looking at the DbEntry for the Director reference before SaveChanges(), and you'll also see Director.Id change each time as well.
There are a few strategies you can use to mitigate this behavior:
Add everything in a single context.
Add the director, then for each movie, only set DirectorId from Director.Id.
Add the director, the for each movie manually set the entry-state for the director object to "unchanged".
In the context, first do a context.Directors.Find(movie.Director.Id); to make sure the director is already in the context before adding the movie.
I have 3x the same Director in my database.
I think you have three different directors, they will have a different ID.
If you want to have just one director you need to let know to the DataContext about him. Maybe you can add a method AddDirector and add the director to the database before to use it.
Then when you do:
firstMovie.Director = director;
secondMovie.Director = director;
thirdMovie.Director = director;
The DataContext will know the director, and you will have the same for your three movies.
I mean I cant check if there is already directory with the same name,
because I am not adding them manually. Entity Framework is adding new
Directory, not me. So, what can I do with it?
For Entity Framework two directors with no ID value are two different directors. The only way to let it know that you want to use the same director is using a director with an ID value.
So you can:
- Create your director with a name and an id new Director { Id = 1, Name="Warner Bros" }
- Use a director that exist in the database (Entity Framework know about him, he has an ID)
Related
I'm using the following code in my startup class to prevent errors serializing my entities which may cause circular references, but it is not working.
Why?
public partial class Startup
{
public static void ConfigureMobileApp(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
new MobileAppConfiguration()
.UseDefaultConfiguration()
.ApplyTo(config);
config.MapHttpAttributeRoutes();
// Use Entity Framework Code First to create database tables based on your DbContext
Database.SetInitializer(new MobileServiceInitializer());
MobileAppSettingsDictionary settings = config.GetMobileAppSettingsProvider().GetMobileAppSettings();
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
config.Services.Add(typeof(IExceptionLogger), new AiExceptionLogger());
if (string.IsNullOrEmpty(settings.HostName))
{
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
// This middleware is intended to be used locally for debugging. By default, HostName will
// only have a value when running in an App Service application.
SigningKey = ConfigurationManager.AppSettings["SigningKey"],
ValidAudiences = new[] { ConfigurationManager.AppSettings["ValidAudience"] },
ValidIssuers = new[] { ConfigurationManager.AppSettings["ValidIssuer"] },
TokenHandler = config.GetAppServiceTokenHandler()
});
}
app.UseWebApi(config);
}
}
According to your description, I created my Azure Mobile App project to test this issue. Based on your Startup.cs, I added my apiController as follows:
[MobileAppController]
public class ValuesController : ApiController
{
[Route("api/values")]
public HttpResponseMessage Get()
{
Department sales = new Department() { Name = "Sales" };
Employee alice = new Employee() { Name = "Alice", Department = sales };
sales.Manager = alice;
return Request.CreateResponse(sales);
}
}
public class Employee
{
public string Name { get; set; }
//[JsonIgnore]
public Department Department { get; set; }
}
public class Department
{
public string Name { get; set; }
public Employee Manager { get; set; }
}
When access this endpoint, I encountered the following XML Circular Object References error:
Note: For a simple way, I removed the XML Formatter via config.Formatters.Remove(config.Formatters.XmlFormatter);. Also, you could refer to the section about preserving object references in XML from Handling Circular Object References.
After I removed XML Formatter, then I encountered the following error about object references loop in JSON:
Then, I followed this Loop Reference handling in Web API code sample, but without luck in the end. Also, I tried to create a new Web API project and found that ReferenceLoopHandling.Ignore could work as expected.
In the end, I found that if I remove the MobileAppController attribute from my
apiController, then it could work as follows:
In summary, I assumed that you could try to ignore the reference attributes with the JsonIgnore for JSON.NET, for more details you could refer to fix 3:Ignore and preserve reference attributes.
Im trying to save a rating against a place, I have the code below, but it doesnt seems to save rating (to the ratings table) for an existing entity
place.Ratings.Add(rating);
_placeRepository.AddPlaceIfItDoesntExist(place);
_placeRepository.Save();
This is the repository method
public void AddPlaceIfItDoesntExist(Place place)
{
var placeItem = context.Places.FirstOrDefault(x => x.GooglePlaceId == place.GooglePlaceId);
if(placeItem==null)
{
context.Places.Add(place);
}
else
{
context.Entry(placeItem).State = EntityState.Modified;
}
}
and this is the poco
public class Place
{
public Place()
{
Ratings = new List<Rating>();
}
public int Id { get; set; }
public string Name { get; set; }
public string GooglePlaceId { get; set; }
}
I think the crux of the problem is because i need to check if the place exists based on googleplaceid(a string) rather than the id (both are unique per place btw)
Here
context.Entry(placeItem).State = EntityState.Modified;
you just mark the existing placeItem object as modified. But it's a different instance than the passed place object, hence contains the orginal values.
Instead, replace that line with:
context.Entry(placeItem).CurrentValues.SetValues(place);
Alternatively, you can use the DbSetMigrationsExtensions.AddOrUpdate method overload that allows you to pass a custom identification expression:
using System.Data.Entity.Migrations;
public void AddPlaceIfItDoesntExist(Place place)
{
context.Places.AddOrUpdate(p => p.GooglePlaceId, place);
}
I have a trouble with EF (6.1.3)
I have created next classes (with many-to-many relationship):
public class Record
{
[Key]
public int RecordId { get; set; }
[Required]
public string Text { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
public class Tag
{
[Key]
public int TagId { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Record> Records{ get; set; }
}
And method:
void AddTags()
{
Record[] records;
Tag[] tags;
using (var context = new AppDbContext())
{
records = context.Records.ToArray();
}//remove line to fix
tags = Enumerable.Range(0, 5).Select(x => new Tag()
{
Name = string.Format("Tag_{0}", x),
Records= records.Skip(x * 5).Take(5).ToArray()
}).ToArray();
using (var context = new AppDbContext()){ //remove line to fix
context.Tags.AddRange(tags);
context.SaveChanges();
}
}
If I use two contexts, the records (which were added to created tags) will be duplicated. If I remove marked rows - problem disappears.
Is there any way to fix this problem without using the same context?
If you can, better reload entities or not detach them at all. Using multiple context instances in application is overall making things much more complicated.
The problem for you comes from the Entity Framework entity change tracker. When you load entitites from your DbContext and dispose that context, entities get detached from entity change tracker, and Entity Framework has no knowledge of any changes made to it.
After you reference detached entity by an attached entity, it (detached entity) immediately gets into entity change tracker, and it has no idea that this entity was loaded before. To give Entity Framework an idea that this detached entity comes from the database, you have to reattach it:
foreach (var record in records) {
dbContext.Entry(record).State = EntityState.Unchanged;
}
This way you will be able to use records to reference in other objects, but if you have any changes made to these records, then all these changes will go away. To make changes apply to database you have to change state to Added:
dbContext.Entry(record).State = EntityState.Modified;
Entity Framework uses your mappings to determine row in database to apply changes to, specifically using your Primary Key settings.
A couple examples:
public class Bird
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Color { get; set; }
}
public class Tree
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
public class BirdOnATree
{
[Column(Order = 0), Key, ForeignKey("Bird")]
public int BirdId { get; set; }
public Bird Bird { get; set; }
[Column(Order = 1), Key, ForeignKey("Tree")]
public int TreeId { get; set; }
public Tree Tree { get; set; }
public DateTime SittingSessionStartedAt { get; set; }
}
Here's a small entity structure so that you could see how it works. You can see that Bird and Tree have simple Key - Id. BirdOnATree is a many-to-many table for Bird-Tree pair with additional column SittingSessionStartedAt.
Here's the code for multiple contexts:
Bird bird;
using (var context = new TestDbContext())
{
bird = context.Birds.First();
}
using (var context = new TestDbContext())
{
var tree = context.Trees.First();
var newBirdOnAtree = context.BirdsOnTrees.Create();
newBirdOnAtree.Bird = bird;
newBirdOnAtree.Tree = tree;
newBirdOnAtree.SittingSessionStartedAt = DateTime.UtcNow;
context.BirdsOnTrees.Add(newBirdOnAtree);
context.SaveChanges();
}
In this case, bird was detached from the DB and not attached again. Entity Framework will account this entity as a new entity, which never existed in DB, even though Id property is set to point to existing row to database. To change this you just add this line to second DbContext right in the beginning:
context.Entry(bird).State = EntityState.Unchanged;
If this code is executed, it will not create new Bird entity in DB, but use existing instead.
Second example: instead of getting bird from the database, we create it by ourselves:
bird = new Bird
{
Id = 1,
Name = "Nightingale",
Color = "Gray"
}; // these data are different in DB
When executed, this code will also not create another bird entity, will make a reference to bird with Id = 1 in BirdOnATree table, and will not update bird entity with Id = 1. In fact you can put any data here, just use correct Id.
If we change our code here to make this detached entity update existing row in DB:
context.Entry(bird).State = EntityState.Modified;
This way, correct data will be inserted to table BirdOnATree, but also row with Id = 1 will be updated in table Bird to fit the data you provided in the application.
You can check this article about object state tracking:
https://msdn.microsoft.com/en-US/library/dd456848(v=vs.100).aspx
Overall, if you can avoid this, don't use object state tracking and related code. It might come to unwanted changes that are hard to find source for - fields are updated for entity when you don't expect them to, or are not updated when you expect it.
folks,
I have a problem when I'm creating new object that has master. The problem actually is that EF is trying to create master object too, but I don't need it.
My two poco classes look like:
[DataContract(IsReference = true)]
public class Application
{
[DataMember]
public int ID { get; set; }
[DataMemeber]
public int ProjectManagerId {get; set; }
[DataMember]
public ProjectManager PM { get; set;}
}
[DataContract(IsReference = true)]
public class ProjectManager
{
[DataMember]
public int ID { get; set; }
[DataMember]
public string FullName { get; set; }
}
When I'm creating new object on ASP.NET MVC page, I've got object of Application class, that has ProjectManagerId equals to 1 and PM field with ID = 0 and FullName for example 'Forest Gump'.
So, when I'm adding object, I have exception that application.PM.ID cannot be 0:
context.Applications.AddObject(application);
context.SaveChanges();
Is it possible to addobject my object without adding master?
I found workaround, but I don't like it: it is assigning PM field to null before adding object to context
application.PM = null;
EF has one core rule you must be aware of. It works with whole object graph and it doesn't allow combining detached and attached entities in the same object graph. It means that both Attach and AddObject method will be always executed on all entities in the object graph (it will traverse your navigation properties and execute the operation recursively).
Because of this essential behavior you must handle existing objects manually. You have three options:
Don't use navigation property. Your Application class has ProjectManagerId exposed. You need only to set this property to ID of the existing manger without populating ProjectManager navigation property to build a relation.
var application = new Application { ProjectManagerId = 1 };
context.Applications.AddObject(application);
context.SaveChanges();
Attach your parent, add child and only after that make connection between them:
// Create attached existing project manager
var projectManager = new ProjectManager { ID = 1 };
context.ProjectManagers.Attach(projectManager);
// Create a new added application
var application = new Applications();
context.Applications.AddObject(application);
// Now you are making relation between two entities tracked by the context
application.ProjectManager = projectManager;
context.SaveChanges();
The last option is simply fixing the state of existing entities. In this case you will set project manger to be unchanged while application will still remain in the added state:
// Add application and its related project manager
context.Applications.AddObject(application);
context.ObjectStateManager.ChangeObjectState(application.ProjectManager, EntityState.Unchanged);
context.SaveChanges();
I'm posting the exact entity:
public class Person : ContactableEntity
{
public Plan Plan { get; set; }
public int Record { get; set; }
public int PersonTypeValue { get; set; }
}
I'm using the following code to update in a disconected context fashion:
public void Update(DbSet MySet, object Obj)
{
MySet.Attach(Obj);
var Entry = this.Entry(Obj);
Entry.State = EntityState.Modified;
this.SaveChanges();
}
This is a method exposed by my dbContext
Called this way:
PersistentManager.Update(PersistentManager.Personas,UpdatedPersona);
The problem is, EF will update any property but the referenced Plan object.
Can someone tell me where is the mistake?
In advance : the entity reaches the point of update with all the properties correctly set.
EF just fails to update the FK in the Database (no exception though)
Update:
tried solving the problem like this but it didn't work:
PersistentMgr.Contacts.Attach(Obj);
PersistentMgr.Entry(Obj).State = EntityState.Modified;
PersistentMgr.Entry(Obj.Plan).State = EntityState.Modified;
PersistentMgr.SaveChanges();
You need...
this.Entry(person).State = EntityState.Modified;
this.Entry(person.Plan).State = EntityState.Modified;
...because when you set the state of the person to Modified the person gets attached to the context in state Modified but related entities like person.Plan are attached in state Unchanged.
If the relationship between Person and Plan has been changed while the entities were detached it is more difficult (especially, like in your model, when no foreign key is exposed as property ("independent association")) to update the entities correctly. You basically need to load the original object graph from the database, compare it with detached graph if relationships have been changed and merge the changes into the loaded graph. An example is here (see the second code snippet in that answer).
Edit
Example to show that it works (with EF 5.0):
using System.Data;
using System.Data.Entity;
using System.Linq;
namespace EFModifyTest
{
public class Person
{
public int Id { get; set; }
public Plan Plan { get; set; }
public int Record { get; set; }
public int PersonTypeValue { get; set; }
}
public class Plan
{
public int Id { get; set; }
public string SomeText { get; set; }
}
public class MyContext : DbContext
{
public DbSet<Person> Contacts { get; set; }
public DbSet<Plan> Plans { get; set; }
}
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());
// Create a person with plan
using (var ctx = new MyContext())
{
ctx.Database.Initialize(true);
var plan = new Plan { SomeText = "Old Text" };
var person = new Person { Plan = plan, Record = 1, PersonTypeValue = 11 };
ctx.Contacts.Add(person);
ctx.SaveChanges();
}
// see screenshot 1 from SQL Server Management Studio
Person detachedPerson = null;
// Load the person with plan
using (var ctx = new MyContext())
{
detachedPerson = ctx.Contacts.Include(c => c.Plan).First();
}
// Modify person and plan while they are detached
detachedPerson.Record = 2;
detachedPerson.PersonTypeValue = 12;
detachedPerson.Plan.SomeText = "New Text";
// Attach person and plan to new context and set their states to Modified
using (var ctx = new MyContext())
{
ctx.Entry(detachedPerson).State = EntityState.Modified;
ctx.Entry(detachedPerson.Plan).State = EntityState.Modified;
ctx.SaveChanges();
}
// see screenshot 2 from SQL Server Management Studio
}
}
}
Screenshot 1 from SQL Server Management Studio (before the modification, Person table is left, Plan table is right):
Screenshot 2 from SQL Server Management Studio (after the modification, Person table is left, Plan table is right):
If it doesn't work for you there must be an important difference to my test model and code. I don't know which one, you must provide more details.
Edit 2
If you change the relationship from Person to another (existing) Plan you must load the original and then update the relationship. With independent associations (no FK property in model) you can update relationships only by using change tracking (aside from more advanced modifications of relationship entries in the ObjectContext change tracker):
var originalPerson = this.Contacts.Include(c => c.Plan)
.Single(c => c.Id == person.Id);
this.Plans.Attach(person.Plan);
this.Entry(originalPerson).CurrentValues.SetValues(person);
originalPerson.Plan = person.Plan;
this.SaveChanges();