I have the two following models and DbContext:
public class TestDbContext : DbContext
{
public IDbSet<Person> People { get; set; }
public IDbSet<Car> Cars { get; set; }
}
public class Person
{
public Person()
{
ID = Guid.NewGuid();
}
public Guid ID { get; set; }
public string Name { get; set; }
public virtual List<Car> Cars { get; set; }
}
public class Car
{
public Car()
{
ID = Guid.NewGuid();
}
public Guid ID { get; set; }
public string Name { get; set; }
public virtual Person Owner { get; set; }
}
I then declare a list of people and a list of cars, setting the owner of the first car to the first person in the list:
List<Person> People = new List<Person>()
{
new Person() {Name = "bill", ID = new Guid("6F39CC2B-1A09-4E27-B803-1304AFDB23E3")},
new Person() {Name = "ben", ID = new Guid("3EAE0303-39D9-4FD9-AF39-EC6DC73F630B")}
};
List<Car> Cars = new List<Car>() { new Car() { Name = "Ford", Owner = People[0], ID = new Guid("625FAB6B-1D56-4F57-8C98-F9346F1BBBE4") } };
I save this off to the database using the following code and this works fine.
using (TestDbContext context = new TestDbContext())
{
foreach (Person person in People)
{
if (!(context.People.Any(p => p.ID == person.ID)))
context.People.Add(person);
else
{
context.People.Attach(person);
context.Entry<Person>(person).State = System.Data.EntityState.Modified;
}
}
foreach (Car caar in Cars)
{
if (!(context.Cars.Any(c => c.ID == caar.ID)))
context.Cars.Add(caar);
else
{
context.Cars.Attach(caar);
context.Entry<Car>(caar).State = System.Data.EntityState.Modified;
}
}
context.SaveChanges();
}
If I then change the owner of the car to the second person and run the code again, the Car owner property doesn't get updated.
Cars[0].Owner = People[1];
Any ideas to what I'm doing wrong? Thanks for any help.
I believe this is a problem of independent vs foreign key association. You are using independent association at the moment and the relation between car and person is actually managed by separate entry object which has its own state (to access this object you must use ObjectContext API). Setting the car's entity entry to modified state will not change the state of the entry for the relation! The simple solution is to use foreign key association instead which means adding new Guid PersonId property to your car and map it as foreign key property for Person navigation property.
If you insist on using independent associations you should change relations only on attached entities otherwise you will had a strong headache with tracking those changes and setting all required entries with correct state. It should be enough to create objects, attach them to context and only after that set the owner of the car - hopefully it will be tracked as a change.
Try something like this :
using (TestDbContext context = new TestDbContext())
{
foreach (Person person in People)
{
if (!(context.People.Any(p => p.ID == person.ID)))
context.People.Add(person);
else
{
context.People.Attach(person);
context.Entry<Person>(person).State = System.Data.EntityState.Modified;
}
context.SaveChanges();
}
foreach (Car caar in Cars)
{
if (!(context.Cars.Any(c => c.ID == caar.ID)))
context.Cars.Add(caar);
else
{
context.Cars.Attach(caar);
context.Entry<Car>(caar).State = System.Data.EntityState.Modified;
}
context.SaveChanges();
}
}
I think your error is due to the context.SaveChanges placements (and partialy your architecture). Consider using a dedicated method (a basic CRUD for instance) for each operation on your DB via Entity Framework. Hope this helps.
Edit : With a CRUD approch :
public class PersonManager // CRUD
{
public void Create(Person person)
{
using (TestDbContext context = new TestDbContext())
{
context.Person.Add(person);
context.SaveChanges();
}
}
public void Update(Person person)
{
using (TestDbContext context = new TestDbContext())
{
context.Person.Attach(person);
context.Entry(person).State = System.Data.EntityState.Modified;
context.SaveChanges();
}
}
}
You could also make this class static in order to fit to your architecture.
Related
I have two classes and they are in one to one relationship
public class Car
{
public long Id { get; set; }
public string Name { get; set; }
public long CompanyId { get; set; }
public Company Company { get; set; }
}
public class Company
{
public long Id { get; set; }
public string Name { get; set; }
public Car Car { get; set; }
}
and let's say I have added one record (with id = 1) to the Company table, so now I want to add a new Car record as
Car c = new Car { Name = "car1" };
c.CompanyId = 1;
context.Cars.Add(c);
context.SaveChanges();
above approach works, I'm OK with that, but if I try the following approach, then it doesn't work and throw an exception(cannot add explicit value to identify column:
Car c = new Car { Name = "car1" };
Company com = new Company { Id = 1 };
c.Company = com;
context.Cars.Add(c);
context.SaveChanges();
so why DbSet.Add() thinks I'm adding a new Company record? can't it be smart enough to tell that there is already a company record whose id is 1 in the Company table?
The DbContext instance will add the relationships on the entity as added (new) items in the database which will be added when the changes are persisted (SaveChanges). You can manually change the state back to Unchanged for any entity that does not have changes.
Car c = new Car { Name = "car1" };
Company com = new Company { Id = 1 };
c.Company = com;
context.Cars.Add(c);
// mark the company as unchanged
context.Entry(com).State = EntityState.Unchanged;
context.SaveChanges();
Alternatively you can query the DbContext for an instance of Company and use that but this is 1 extra roundtrip to the database. The upside of this approach is the code might be easier to read/maintain.
It's because by default the Entity framework tries to add two objects to the database with a relationship between them when you are creating a relationship between two new objects. You can prevent adding child objects with the:
context.Entry(com).State = EntityState.Detached
or you can just try
Car c = new Car {
Name = "car1",
Company = context.Companies.First(x => x.Id == 1)
};
context.Cars.Add(c);
context.SaveChanges();
I have added a prop to my class and added a new DbSet but when saving, it does not store my child objects.
My house class:
[Table("Houses")]
public class House
{
[Key]
public int ID { get; set; }
public List<Price> Prices { get; set; } // <-- new prop
}
my dbcontext has a Prices prop now: public DbSet<Price> Prices { get; set; } and I enabled migrations, added the migration and updated the database. So the prices table is created.
When I update a House object, it does not insert anything in the Prices table.
var h = new House(); // with prices etc filled
if (db.Houses.Any(hc => hc.Code.Equals(h.Code, StringComparison.InvariantCultureIgnoreCase)))
{
var original = db.Houses.First(k => k.Code.Equals(h.Code, StringComparison.InvariantCultureIgnoreCase));
h.ID = original.ID; // workaround for The property 'ID' is part of the object's key information and cannot be modified.
h.CountryId = original.CountryId;
db.Entry(original).CurrentValues.SetValues(h);
db.SaveChanges(); // does update House fields, but no prices
} else { // add new
db.Houses.Add(h);
db.SaveChanges();
}
I did add public virtual House House { get; set; } to my Price class. But I do not fill it, because when populating the house object, I do not know the ID in the db yet. Maybe that is causing it? I have also read https://stackoverflow.com/a/26572122/169714 and added this to my Price class:
[ForeignKey("HouseId")]
public virtual House House { get; set; }
public int HouseId { get; set; }
but still no entries in the prices table. I am probably doing something wrong storing/updating the database.
edit current store method:
using (var db = new MyCommon.HouseContext())
{
var l = db.Countries.First(tmpC => tmpC.Code.Equals(h.Country.Code));
h.OperatorId = op.ID;
h.CountryId = l.ID;
h.Country = l;
var existingHouse = db.Houses.Where(p => p.Code.Equals(h.Code, StringComparison.InvariantCultureIgnoreCase)).SingleOrDefault();
if (existingHouse != null)
{
// update
h.ID = existingHouse.ID; // workaround for The property 'ID' is part of the object's key information and cannot be modified.
h.CountryId = existingHouse.CountryId;
h.OperatorId = existingHouse.OperatorId;
db.Entry(existingHouse).CurrentValues.SetValues(h);
db.Entry(existingHouse).State = System.Data.Entity.EntityState.Modified;
//db.SaveChanges(); // moved to bottom for perf.
}
else
{
existingHouse = h;
db.Houses.Add(h); // insert
}
foreach (var ft in existingHouse.Prices)
{
var existingFt = existingHouse.Prices.SingleOrDefault(f => f.ID == ft.ID);
if (existingFt != null)
{
db.Entry(existingFt).CurrentValues.SetValues(ft);
db.Entry(existingFt).State = System.Data.Entity.EntityState.Modified;
}
else
{
existingHouse.Prices.Add(ft);
}
}
db.SaveChanges();
}
Check the EntityState of your objects. Attach the object to your context, and iterate through these Prices to mark the EntityState.Modified. If the Price objects are new, you can use EntityState.Added. You can see here for an 'upsert' pattern example.
I'm using Castle ActiveRecord 3.0 and I have a problem with object references when fetching them from the database. When I'm fetching multiple entities within the same SessionScope I get the same object references for related entities.But often i can't use the same session scope to fetch all the objects I need. It's kinda hard to describe it so here's an example:
[ActiveRecord(Table = "car")]
public class Car : ActiveRecordLinqBase<Car>
{
[PrimaryKey("id")]
public virtual int Id { get; set; }
[OneToOne(PropertyRef = "Car", Cascade = CascadeEnum.SaveUpdate)]
public virtual Location Location { get; set; }
}
[ActiveRecord("location")]
public class Location : ActiveRecordLinqBase<Location>
{
[PrimaryKey(Column = "id")]
public virtual int Id { get; set; }
[BelongsTo("car_id", NotFoundBehaviour = NotFoundBehaviour.Ignore,
Cascade = CascadeEnum.SaveUpdate)]
public virtual Car Car { get; set; }
}
[ActiveRecord(Table = "move_order")]
public class MoveOrder : ActiveRecordLinqBase<MoveOrder>
{
[PrimaryKey(Column = "id")]
public virtual int Id { get; set; }
[BelongsTo(Column = "car_id")]
public virtual Car Car { get; set; }
[BelongsTo(Column = "destination_id")]
public virtual Location Destination { get; set; }
}
I'm creating some objects to start with:
using (new SessionScope())
{
var car = new Car() { };
var initialLocation = new Location() { Car = car };
initialLocation.Save();
var destinationLocation = new Location();
destinationLocation.Save();
}
Then if I create 2 move orders for the same Car/Location it works just fine:
MoveOrder moveOrder;
MoveOrder moveOrder2;
using (new SessionScope())
{
moveOrder = new MoveOrder()
{
Destination = Location.Queryable.First(x => x.Car == null),
Car = Car.Queryable.First();
};
moveOrder2 = new MoveOrder()
{
Destination = Location.Queryable.First(x => x.Car == null),
Car = Car.Queryable.First()
};
}
Object references are equal for:
moveOrder.Car, moveOrder2.Car
moveOrder.Location, moveOrder2.Location
But when the I use different session scopes to get both MoveOrders:
using (new SessionScope())
{
moveOrder = new MoveOrder()
{
Destination = Location.Queryable.First(x => x.Car == null),
Car = Car.Queryable.First()
};
}
and
using (new SessionScope())
{
moveOrder2 = new MoveOrder()
{
Destination = Location.Queryable.First(x => x.Car == null),
Car = Car.Queryable.First()
};
}
None of those properties are reference equal, but logically those are the same entities.
I also tried with getting Nhibernate session and associating all detached objects back into new session:
using (var sess = GetSession())
{
sess.Lock(moveOrder.Destination, LockMode.Read);
sess.Lock(moveOrder.Car, LockMode.Read);
moveOrder2 = new MoveOrder()
{
Destination = sess.Get<Location>(moveOrder.Destination.Id),
Car = sess.Get<Car>(moveOrder.Car.Id),
};
}
This does the trick in this case, but has some issues:
I have to pass EVERY database entity I currently use to the new session
I cannot use queries, session.Get works for Ids
The main question is:
How can I achieve getting the same object references from DB every time I fetch the same entities (same Ids)?
Also tried to use Nhibernate second level cache by configuring it in AR init and adding Cache = CacheEnum.ReadWrite to every db class ActiveRecord attribute - with no result.
I'd rather not keep the same session open all time.
To illustrate my problem, suppose I have a data model where I have a collection of Books, each of which has one or more Drafts, and also a "current" Draft. I'd like my model to look something like this:
class Book
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<Draft> Drafts { get; set; }
public virtual Draft CurrentDraft { get; set; }
}
class Draft
{
public int Id { get; set; }
public virtual int BookId { get; set; }
public virtual Book Book { get; set; }
public string Description { get; set; }
}
I have a DbContext that looks like this:
class TestDbContext : DbContext
{
public DbSet<Book> Books { get; set; }
public DbSet<Draft> Drafts { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
base.OnModelCreating(modelBuilder);
}
}
...and a simple program like this:
static void Main(string[] args)
{
Database.SetInitializer<TestDbContext>(new DropCreateDatabaseAlways<TestDbContext>());
using (var db = new TestDbContext())
{
var book = new Book() { Title = "War and Peace", Drafts = new List<Draft>() };
var draft1 = new Draft() { Book = book, Description = "First Draft" };
book.Drafts.Add(draft1);
var draft2 = new Draft() { Book = book, Description = "Second Draft" };
book.Drafts.Add(draft2);
book.CurrentDraft = draft2;
db.Books.Add(book);
db.SaveChanges();
foreach (var b in db.Books)
{
Console.WriteLine("Book {0}: {1}", b.Id, b.Title);
foreach (var d in book.Drafts)
Console.WriteLine("\tDraft ID {0}: {1} from Book ID {2}", d.Id, d.Description, d.BookId);
Console.WriteLine("Current draft has ID {0}", b.CurrentDraft.Id);
}
Console.ReadKey();
}
}
When the DB is created by EF, it looks like this:
[I'm not wild about the extra Book_Id column, but I could live with it if it worked.]
Sadly, when I run the test program, it fails at the SaveChanges call, with an exception:
An error occurred while saving entities that do not expose foreign key
properties for their relationships. The EntityEntries property will
return null because a single entity cannot be identified as the source
of the exception. Handling of exceptions while saving can be made
easier by exposing foreign key properties in your entity types. See
the InnerException for details.
I've tried messing about with the fluent API in my OnModelCreating, but when I try to configure it that way it gets confused by the fact that there are two FK relationships between Books and Drafts. I'm not familiar enough with the fluent stuff (or EF generally) to know how to do it properly.
Is this even possible in EF code first?
Your inner exception is that EF can't figure out the order to save things to the database to create something properly. If you call save changes in multiple places to force the order your test app will work:
var book = new Book() { Title = "War and Peace", Drafts = new List<Draft>() };
db.Books.Add(book);
db.SaveChanges();
var draft1 = new Draft() { Book = book, Description = "First Draft" };
book.Drafts.Add(draft1);
var draft2 = new Draft() { Book = book, Description = "Second Draft" };
book.Drafts.Add(draft2);
book.CurrentDraft = draft2;
db.SaveChanges();
Regarding the second book_id - you could use this fluent:
modelBuilder.Entity<Book>()
.HasMany(b => b.Drafts)
.WithRequired(d => d.Book)
.HasForeignKey(d => d.BookId);
modelBuilder.Entity<Book>()
.HasOptional(b => b.CurrentDraft)
.WithOptionalDependent()
.Map(m => m.MapKey("CurrentDraftId"));
which produces this database:
If i were you i will add one more property to Draft that will be bool IsCurrent{get;set;}. And
public Draft CurrentDraft { get{return Drafts.SingleOrDefault(c=>c.IsCurrent)} }
In this case there will be one foreign key between books and drafts.
I'm working on a small sample project using Entity Framework 4.1 (code first). My classes look like this:
public class Context : DbContext
{
public IDbSet<Person> People { get; set; }
public IDbSet<EmployeeType> EmployeeTypes { get; set; }
}
public class Person
{
[Key]
public int Key { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
virtual public EmployeeType EmployeeType { get; set; }
}
public class EmployeeType
{
[Key]
public int Key { get; set; }
public string Text { get; set; }
virtual public ICollection<Person> People { get; set; }
}
I've saved a couple EmployeeTypes ("first", "second") to the database, and I've saved a Person who has the first type. Now I want to modify the Person. I know I can do this by loading the Person, changing properties, and then saving. But what I want to do instead, which seems to me like it ought to work, is this:
var c = new Context();
var e = c.EmployeeTypes.Single(x => x.Text.Equals("second"));
var p = new Person {
Key = originalKey, // same key
FirstName = "NewFirst", // new first name
LastName = "NewLast", // new last name
EmployeeType = e }; // new employee type
c.Entry(p).State = EntityState.Modified;
c.SaveChanges();
Oddly, this changes FirstName and LastName but not EmployeeType. If I get a new Context and request this Person, the EmployeeType remains set to "first" as it was before this code ran.
What do I need to do to get the navigation properties to update, and not just the scalar properties? (This is especially puzzling because for EmployeeType, the only thing that actually needs to change is the foreign key in the Person table, and that key is a scalar property.)
(By the way, I know I can do this by retrieving the Person first, then changing properties one-by-one, but as I'm using model binding in ASP.NET MVC, it seems like this way would be easier because I'll have the updated person object already in my POST method.)
You can try it different way:
var c = new Context();
var e = c.EmployeeTypes.Single(x => x.Text.Equals("second"));
var p = new Person {
Key = originalKey, // same key
FirstName = "NewFirst", // new first name
LastName = "NewLast"}; // new last name
c.People.Attach(p); // Attach person first so that changes are tracked
c.Entry(p).Reference(e => e.EmployeeType).Load();
p.EmployeeType = e; // Now context should know about the change
c.Entry(p).State = EntityState.Modified;
c.SaveChanges();
Other approach is exposing foreign key in your Person entity like:
public class Person
{
[Key]
public int Key { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[ForeignKey("EmployeeType")]
public int EmployeeTypeKey { get; set; }
public virtual EmployeeType EmployeeType { get; set; }
}
This will change the type of relation between Person and EmployeeType from Independent association to Foreign key association. Instead of assigning the navigation property assign the foreign key property. This will allow you to modify relation by your current code.
Problem is that independent associations (those don't using foreign key property) are handled as separate object in state manager / change tracker. So your modification of the person didn't affect state of the existing relation neither set the new relation. I asked on MSDN how to do it with DbContext API but it is possible only if you cast DbContext to ObjectContext and use ObjectStateManager and ChangeRelationshipState.
After trying a dozen different ways to do it the EF way, I concluded that there isn't a reasonable EF Code First way to do what I'm trying to do. So I used reflection. I created this method for my class that inherits from DbContext:
public void UpdateFrom<T>(T updatedItem) where T : KeyedItem
{
var originalItem = Set<T>().Find(updatedItem.Key);
var props = updatedItem.GetType().GetProperties(
BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in props)
{
var value = prop.GetValue(updatedItem, null);
prop.SetValue(originalItem, value, null);
}
}
All my objects inherit from an abstract class and have a primary key property in common, so this finds the existing object with the same key as the one passed in, and then updates the existing object's from the new one. SaveChanges needs to be called afterwards.
This works for collections, although i feel like there's got to be a better way.
var properties = typeof(TEntity).GetProperties();
foreach (var property in properties)
{
if (property.GetCustomAttributes(typeof(OneToManyAttribute), false).Length > 0)
{
dynamic collection = db.Entry(e).Collection(property.Name).CurrentValue;
foreach (var item in collection)
{
if(item.GetType().IsSubclassOf(typeof(Entity)))
{
if (item.Id == 0)
{
db.Entry(item).State = EntityState.Added;
}
else
{
db.Entry(item).State = EntityState.Modified;
}
}
}
}
}
db.Entry(e).State = EntityState.Modified;
db.SaveChanges();