Entity Framework Core navigations not updating - entity-framework-core

I'm using Entity Framework Core v5.0.3 for this.
I'm making a simple information app for users where each user can have favourite colours.
Person model
public class Person
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PersonId { get; set; }
[MaxLength(50)]
public string FirstName { get; set; }
[MaxLength(50)]
public string LastName { get; set; }
public bool IsAuthorised { get; set; }
public bool IsValid { get; set; }
public bool IsEnabled { get; set; }
public virtual ICollection<FavouriteColour> FavouriteColours { get; set; }
}
Favourite colour model
public class FavouriteColour
{
[Key]
public int PersonId { get; set; }
public virtual Person Person { get; set; }
[Key]
public int ColourId { get; set; }
public virtual Colour Colour { get; set; }
}
Colour model
public class Colour
{
[Key]
public int ColourId { get; set; }
public string Name { get; set; }
public bool IsEnabled { get; set; }
public virtual IList<FavouriteColour> FavouriteColours { get; set; }
}
In my database context I've defined them as so
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<FavouriteColour>()
.HasKey(c => new { c.PersonId, c.ColourId });
modelBuilder.Entity<FavouriteColour>()
.HasOne(fc => fc.Person)
.WithMany(p => p.FavouriteColours)
.HasForeignKey(p => p.PersonId);
modelBuilder.Entity<FavouriteColour>()
.HasOne(fc => fc.Colour)
.WithMany(c => c.FavouriteColours)
.HasForeignKey(c => c.ColourId);
}
Now in the app a user can add or remove favourite colours so a new user object is received in the controller which calls the repository update
public async Task<IActionResult> PutPerson(int id, Person person)
{
peopleRepository.Update(person);
}
The repository then does the update
public void Update(Person person)
{
_context.Update(person);
}
After doing await _context.SaveChangesAsync(); none of the colours change. I thought the purpose of all these models and such was so the colours would change automatically?
I setup a FavouriteColourRepository instead to do the updates like this
public async Task<bool> Update(int personId, ICollection<FavouriteColour> favouriteColours)
{
// empty their favourite colours
_context.FavouriteColours.RemoveRange(_context.FavouriteColours.Where(fc => fc.PersonId == personId);
// add new favourite colours
_context.FavouriteColours.AddRange(favouriteColours);
return true;
}
And I changed my controller to this
public async Task<IActionResult> PutPerson(int id, Person person)
{
peopleRepository.Update(person);
bool valid = await favouriteColourRepository.Update(id, person.FavouriteColours);
}
But for some reason I can't figure out
_context.FavouriteColours.RemoveRange(_context.FavouriteColours.Where(fc => fc.PersonId == personId);
actually alters the favouriteColours parameter and forces the last state into it which creates duplicate primary keys and the inserting fails.
So why do the new favourite colours never get inserted and why is my favouriteColours parameter being edited when I'm trying to clear all the colours a user already has?

why do the new favourite colours never get inserted
The call _context.Update(person); puts the Person entity and all is related FavouriteColour entity in Modified state. Hence, on the next SaveChanges call EF is supposed to submit update commands to the database.
If you modify any property of the Person entity, you'll find that the person has been updated correctly. The issue with the FavouriteColour entity is that it contains nothing but the primary key properties, and EF does not modify the key property (or any property that is part of a composite primary key). You can test this with the following code -
var fc = dbCtx.FavouriteColours
.FirstOrDefault(p => p.PersonId == 1 && p.ColourId == 4);
fc.ColourId = 6; // CoulourId is part of primary key
dbCtx.SaveChanges();
and you will be met with an exception -
The property 'FavouriteColour.ColourId' is part of a key and so cannot
be modified or marked as modified. ...
Therefore, EF will not even generate any update command for the FavouriteColours, even though all of them are marked as Modified.
To update the Person and all its FavouriteColour you need to do something like -
// fetch the exising person with all its FavouriteColours
var existingPerson = dbCtx.Persons
.Include(p => p.FavouriteColours)
.FirstOrDefault(p => p.Id == id);
// modify any properties of Person if you need
// replace the existing FavouriteColours list
existingPerson.FavouriteColours = person.FavouriteColours;
// save the changes
dbCtx.SaveChanges();
This will -
delete any FavouriteColour that are in the existing list but not in the new list
insert any FavouriteColour that are in the new list but not in the existing list.
With your repositories in place, its up to you how you implement this.
why is my favouriteColours parameter being edited when I'm trying to
clear all the colours a user already has
Before this operation you called _context.Update(person);. Therefore, Person is now being tracked in Modified state as an existing entity. Then when you called -
_context.FavouriteColours.RemoveRange(_context.FavouriteColours.Where(fc => fc.PersonId == personId));
the _context.FavouriteColours.Where(fc => fc.PersonId == personId) part, fetched the existing FavouriteColours of that Person from the database. So these fetched FavouriteColours are added to the person's FavouriteColours list (because it is being tracked).
For example, lets say in your controller you received a Person entity with 4 FavouriteColour, and the database has 3 FavouriteColour for this person. After calling _context.Update(person);, the person is being tracked with a list of 4 FavouriteColour. Then when you fetched this person's existing FavouriteColours from the database, the person is being tracked with a list of total 7 FavouriteColour.
I hope that shades any light.

Related

Having nullable foreign keys in one-to-one relationship with more than one entity

I wanted Movie, Actor, Director, User etc entities to have exactly one Image and an Image to belong to exactly one entity. I defined and configured them as -
Models : (simplified)
public class Movie
{
public int Id { get; set; }
public string Title { get; set; }
public Image Image { get; set; }
}
public class Actor
{
public int Id { get; set; }
public string Name { get; set; }
public Image Image { get; set; }
}
// Director, User etc are defined in similar way
public class Image
{
public int Id { get; set; }
public string Base64 { get; set; }
public int? MovieId { get; set; }
public int? ActorId { get; set; }
public int? DirectorId { get; set; }
public int? UserId { get; set; }
}
Configurations : (simplified)
public class MovieConfig : IEntityTypeConfiguration<Movie>
{
public void Configure(EntityTypeBuilder<Movie> builder)
{
builder.ToTable("Movie");
builder.HasKey(p => p.Id);
builder.Property(p => p.Title).IsRequired(true).HasColumnType("nvarchar(128)");
builder.HasOne(e => e.Image)
.WithOne()
.HasForeignKey<Image>(e => e.MovieId)
.OnDelete(DeleteBehavior.Cascade);
}
}
// Actors, Director, User etc are configured in similar way
public class ImageConfig : IEntityTypeConfiguration<Image>
{
public void Configure(EntityTypeBuilder<Image> builder)
{
builder.ToTable("Image");
builder.HasKey(p => p.Id);
builder.Property(p => p.Base64).IsRequired(true).HasColumnType("nvarchar(MAX)");
}
}
This generates the schema with Image table having one-to-one relationship with each of Movie, Actor, Director, User etc table, as expected.
What's bothering me are all those nullable foreign-key fields in Image, because -
a new nullable foreign-key must be added whenever a new entity with Image is introduced
for any image entry only one of those foreign-key columns is going to have a value
What other ways I could define and configure the entities to achieve the same result while avoiding those nullable foreign-keys?
Edit :
Or is it, in general practice, considered OK to have a schema like the one I currently have (with multiple nullable foreign-keys where only one of them can have a value)?
I don't have a lot of experience in database design/schema and best/general practices. It just felt wrong to me and that's where the question came.
So, please feel free to give your opinion and suggestion?
Create relationship for MovieId, ActorId, DirectorId and UserId in Image table in sql server.
Then re-update your dbcontext and those foreign IDs will be auto saved from movie, actor, director and user when inserting.

How to properly define DbContext derived class when using EF Code First with DDD methodology?

I am new to EF, Code First and DDD and still in the learning process, so let's say I have simple domain like this
public class Customer
{
public string Name { get; set; }
public string Address { get; set; }
public List<Order> Orders { get; set; }
}
public class Order
{
public DateTime OrderDate { get; set; }
public List<LineItem> LineItems { get; set; }
}
public class LineItem
{
public Product Product { get; set; }
public int Quantity { get; set; }
}
public class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
}
After domain is defined the next step is to create DbContext derived class and my question is how should it look like? What is a context class definition driven by? Is it by the use cases of the final application?
For example, looking at the domain above we can see that given the Customer instance we can access any child object. So is it then enough to make the context class contains only Customers property look like this:
class MyContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
}
With this I can browse customers from my app and then select one to see details and orders history etc... Looks good for now.
Now let's say I want following feature in my application:
- list last 10 orders in store (no matter who was the customer)
- list all orders with specific product in it
I guess that this data can be pulled from the current context right? Eg. last 10 orders:
using (var context = new MyContext())
{
var lastTenOrders = context.Customers.Include("Orders")
.Select(customer => customer.Orders)
.SelectMany(orderList => orderList)
.OrderByDescending(order => order.OrderDate)
.Take(10)
.ToList();
}
And getting all orders that contain product with Id = 5:
using (var context = new MyContext())
{
int productId = 5;
var lastTenOrders = context.Customers.Include("Orders")
.Select(customer => customer.Orders)
.SelectMany(orderList => orderList)
.Where(order => order.LineItems.Where(i => i.Product.Id == productId).Any())
.ToList();
}
(note I didn't test these queries so not sure if they work but they describe my general idea)
So I guess this would work but I am wondering is this the right path. Queries can get pretty complex here and it would probably be easier if I add say Orders and Products to the DbContext:
public class MyContext : DbContext
{
public DbSet Customers { get; set; }
public DbSet Orders { get; set; }
public DbSet Products { get; set; }
}
On the other hand I am not sure if I should add orders since they can already be retrieved from the Customer etc...
To sum it up, what are best practices when defining DbContext (and domain model for that matter) and should that be driven by the features (use cases) of the application? Feel free to use and change code in my example above in your explanation.
Consider protected setters for your properties.
Otherwise you could end up with 'data entities' rather then a proper domain model.
public class Customer
{
public string Address { get; protected set; }
public MoveTo(string newAddress)
{
if (newAddress == null) throw new ArgumentNullException("newAddress");
// and other address sanity checks..
Address = newAddress;
DomainEvent.Publish(new CustomerMoved(CustomerNumber, newAddress));
}
}

How can I have a Foo with exactly one Bar in EF 5 Code First

I'm a gibbering wreck trying to get EF code first to let me do something that I could do in 2 minutes in SQL. Had I not already spent 5 days trying to get it to work, I'd just code up my database in DDL and use ADO.NET. But I digress...
I want to have 2 tables, where each record in A has a corresponding record in B. They're both part of the same object; they need to be in separate tables for reasons I won't go into (but they really do, so don't go there). If I were designing it from the database end, I'd simply have an FK relationship from B to A. Job done.
In EF Code First, I've tried using both the shared primary key method and the one-to-one foreign key association method, and neither work for me. I've also tried 100 or so combinations of all the variants I can think of, and I'm no further forward.
As I said, all I want is there to be a navigable relationship from A to B (and back would be nice, but I've read that's not possible), and for that relationship to be lazy-loaded, so that I can say a.b and have access to the fields of b.
I can't possibly enumerate all the things I've tried, so let me just give an example of what nearly works:
class Foo
{
public int Id { get; set; }
public string FooProperty { get; set; }
public virtual Bar Bar { get; set; }
}
class Bar
{
public int Id { get; set; }
public string BarProperty { get; set; }
}
Note that there's no back-reference from Bar to Foo, since (a) SQL Server would complain about multiple cascade delete paths, and (b) EF would complain about not knowing which side is the principal end of the association. So... fine - I can live without it.
What this gets me in the database is a Foos table with Id, FooProperty and Bar_Id fields, and a Bars table with Id and BarProperty fields. That's pretty close to they way I'd model it in SQL, although I'd probably put the FK field in Bar rather than Foo. But since it's 1:1 it doesn't really matter, I guess.
The reason I say that this nearly works is that if I add a Bar and associated Foo and then load them back in, the Bar property of the Foo object is null.
using (var dbContext = new MyDbContext())
{
var foo = dbContext.Foos.Create();
foo.FooProperty = "Hello";
dbContext.Foos.Add(foo);
var bar = dbContext.Bars.Create();
bar.BarProperty = "world";
foo.Bar = bar;
dbContext.SaveChanges();
}
using (var dbContext = new MyDbContext())
{
foreach (var foo in dbContext.Foos)
Console.WriteLine(foo.Bar.Id); // BOOM! foo.Bar is null
}
I would normally expect the evaluation of foo.Bar to trigger lazy-loading of the Bar object, but it doesn't - that property remains null.
How can I fix it?
Thsi should work:
Context
public class FoobarCtx : DbContext
{
public DbSet<Bar> Bars { get; set; }
public DbSet<Foo> Foos { get; set; }
public FoobarCtx()
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Bar>()
.HasRequired(f => f.Foo)
.WithRequiredDependent(b => b.Bar)
.Map(x => x.MapKey("FooId"))
.WillCascadeOnDelete(true);
}
}
Entities
public class Foo
{
public int Id { get; set; }
public string Foo1 { get; set; }
public string Foo2 { get; set; }
public virtual Bar Bar { get; set; }
}
public class Bar
{
public int Id { get; set; }
public string Bar1 { get; set; }
public string Bar2 { get; set; }
public virtual Foo Foo { get; set; }
}
I tested it in EF 4.3, but I think it should work also in EF5. The Key is the OnModelCreating method. There you can define either one or the other as the principal/descendant and get rid of the Microsoft SQL restriction.
For more information see this blog-post. For more information about the model builder (fluent API), go here.
To enable lazy loading, use the DbContext.FooSet.Create() method. Example here.
As a reminder to myself, if nothing else...
LueTm arrived at a solution that produced the table structure I originally had in mind (with a FooId column in the Bar table), and independent PKs on the two tables. It was still not possible to access the Foo.Bar property without first loading it using dbContext.Foos.Include(f => f.Bar).
I was also able to get things to work pretty well with a shared primary key (both tables have a PK, but only the one in Foos is an identity (auto-increment) column, and there's an FK relationship from the Id in Bars to the Id in Foos.
To do this, I had a Bar property in the Foo class, and a Foo property in the Bar class (so 2-way navigation works), and put the following in my OnModelCreating.
modelBuilder.Entity<Bar>()
.HasRequired(x => x.Foo)
.WithRequiredDependent(x => x.Bar)
.WillCascadeOnDelete(true);
modelBuilder.Entity<Foo>()
.HasRequired(x => x.Bar)
.WithRequiredPrincipal(x => x.Foo)
.WillCascadeOnDelete(true);
(I'm not sure that the second call here actually does anything).
But again, you still need the Include() call in order to be able to access foo.Bar.
I just went through this myself. I have a FoodEntry, with a Food, and the Food has a FoodGroup.
public class FoodEntry
{
public int Id { get; set; }
public string User { get; set; }
public Food Food { get; set; }
public decimal PortionSize { get; set; }
public Portion Portion { get; set; }
public int Calories { get; set; }
public Meal Meal { get; set; }
public DateTime? TimeAte { get; set; }
public DateTime EntryDate { get; set; }
}
public class Food
{
public int Id { get; set; }
public string Name { get; set; }
public FoodGroup FoodGroup { get; set; }
}
public class FoodGroup
{
public int Id { get; set; }
public string Name { get; set; }
}
I'm using code first and my POCO classes are defined just as yours are. I do not have the properties marked virtual.
In any event, the database generates as I would expect - with foreign keys as you described.
But this query resulted in just getting the FoodEntry collection with nulls in the Food, Portion and Meal properties:
var foodEntries = db.FoodEntries
.Where(e => e.User == User.Identity.Name).ToList();
I changed the query to this, and the entire graph loaded for my collection:
var foodEntries = db.FoodEntries
.Include( p => p.Food)
.Include(p => p.Food.FoodGroup)
.Include(p => p.Portion)
.Include( p => p.Meal)
.Where(e => e.User == User.Identity.Name).ToList()
;
For a 1:1 relationship in addition to a 1:many relationship (Foo has several Bars and also a CurrentBar):
modelBuilder.Entity<Foo>()
.HasOptional(f => f.CurrentBar)
.WithMany()
.HasForeignKey(f => f.CurrentBarId);
modelBuilder.Entity<Bar>()
.HasRequired(b => b.Foo)
.WithMany(f => f.Bars)
.HasForeignKey(b => b.FooId);

Entity Framework - Adding parent is also trying to add child when I don't want it to

I have two objects (WishListItem and Product) in a one-to-many relationship. WishListItem has to have a product. Each Product can be in 0 - n WishListItems.
public class WishListItem
{
public int Id { get; set; }
public int ProductId { get; set; }
public Product Product { get; set; }
}
public class Product
{
public int Id { get; set; }
// ... other properties
}
The Product has no knowledge of WishListItems. All of the Products exist. I just want to add a new WishListItem. My WishListItem model for the relationship is this:
HasRequired(p => p.Product).WithMany().HasForeignKey(p => p.ProductId);
When I try to add a new item like this:
WishListItem item = new WishListItem();
// ... sets properties
WishListItems.Add(item); // WishListItems is of type DbSet<WishListItem>
SaveChanges();
This code seems to try to also add a Product. I don't want to add a new Product (or even update it). The Product property is set to null. How do I tell Entity Framework that I only want to add the WishListItem? Do I need to Ignore the Product property (by doing Ignore(p => p.Product); in my WishListItem model) and load the Product separately whenever I load my WishListItem objects?
I have solved my issue. The problem came from another property on the Product object.
private bool _isFreeShippingInitialValue;
public bool IsFreeShipping
{
get
{
return _isFreeShippingInitialValue ||
computedValueFromAnotherChildObject;
}
set
{
_isFreeShippingInitialValue = value;
}
}
We noticed that when you first get the Product object, the IsFreeShipping.get is called (not sure why) before any child objects are loaded. For example, if _isFreeShippingInitialValue is false and computedValueFromAnotherChildObject is true, IsFreeShipping first returns false (because computedValueFromAnotherChildObject is first false since no child objects have been loaded), then true the next time you try to get IsFreeShipping. This makes EF think the value has changed.
The first item we added to WishListItems worked fine. It was the second item that broke. We believe SaveChanges() (or something prior to it) loaded the Product for the first WishListItem. The SaveChanges() was breaking on the Product of the first WishListItem when we were adding the second item.
So, in short, be careful when computing values in a Property.get using child objects because it can bite you in the butt.
This works for me without adding any new Addresses records. In this model, Person has an optional home address, but address doesn't have any knowledge of the person.
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual Address HomeAddress { get; set; }
public int HomeAddress_id { get; set; }
}
public class Address
{
public int Id { get; set; }
public string PhoneNumber { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Country { get; set; }
}
In the DbContext override, I have the below code
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasRequired(t => t.HomeAddress).WithMany()
.HasForeignKey(t => t.HomeAddress_id);
}
I can write a unit test like this.
var addressId = 0;
using (var db = new DataContext())
{
var address = new Address { City = "test", Country = "test", PhoneNumber = "test", State = "test", Street = "test" };
db.Addresses.Add(address);
db.SaveChanges();
addressId = address.Id;
}
using (var db = new DataContext())
{
var person = new Person { Email = "test#test.com", FirstName = "Testy", LastName = "Tester", HomeAddress_id = addressId };
db.Persons.Add(person);
db.SaveChanges();
}

Entity Framework, Code First, Update "one to many" relationship with independent associations

It took me way too long to find a solution to the scenario described below. What should seemingly be a simple affair proved to be rather difficult. The question is:
Using Entity Framework 4.1 (Code First approach) and "Independent associations" how do I assign a different end to an existing "many to one" relationship in a "detached" scenario ( Asp.Net in my case).
The model:
I realize that using ForeignKey relationships instead of Independent Associations would have been an option, but it was my preference to not have a ForeignKey implementation in my Pocos.
A Customer has one or more Targets:
public class Customer:Person
{
public string Number { get; set; }
public string NameContactPerson { get; set; }
private ICollection<Target> _targets;
// Independent Association
public virtual ICollection<Target> Targets
{
get { return _targets ?? (_targets = new Collection<Target>()); }
set { _targets = value; }
}
}
A Target has one Customer:
public class Target:EntityBase
{
public string Name { get; set; }
public string Description { get; set; }
public string Note { get; set; }
public virtual Address Address { get; set; }
public virtual Customer Customer { get; set; }
}
Customer derives from a Person class:
public class Person:EntityBase
{
public string Salutation { get; set; }
public string Title { get; set; }
public string FirstName { get; set; }
public string LastName { get; set ; }
public string Telephone1 { get; set; }
public string Telephone2 { get; set; }
public string Email { get; set; }
public virtual Address Address { get; set; }
}
EntityBase class provides some common properties:
public abstract class EntityBase : INotifyPropertyChanged
{
public EntityBase()
{
CreateDate = DateTime.Now;
ChangeDate = CreateDate;
CreateUser = HttpContext.Current.User.Identity.Name;
ChangeUser = CreateUser;
PropertyChanged += EntityBase_PropertyChanged;
}
public void EntityBase_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (Id != new Guid())
{
ChangeDate = DateTime.Now;
ChangeUser = HttpContext.Current.User.Identity.Name;
}
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, e);
}
public event PropertyChangedEventHandler PropertyChanged;
public Guid Id { get; set; }
public DateTime CreateDate { get; set; }
public DateTime? ChangeDate { get; set; }
public string CreateUser { get; set; }
public string ChangeUser { get; set; }
}
The Context:
public class TgrDbContext : DbContext
{
public DbSet<Person> Persons { get; set; }
public DbSet<Address> Addresses { get; set; }
public DbSet<Customer> Customers { get; set; }
public DbSet<Target> Targets { get; set; }
public DbSet<ReportRequest> ReportRequests { get; set; }
// If OnModelCreating becomes to big, use "Model Configuration Classes"
//(derived from EntityTypeConfiguration) instead
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>().HasOptional(e => e.Address);
modelBuilder.Entity<Customer>().HasMany(c => c.Targets).WithRequired(t => t.Customer);
}
public static ObjectContext TgrObjectContext(TgrDbContext tgrDbContext)
{
return ((IObjectContextAdapter)tgrDbContext).ObjectContext;
}
}
I waited for #Martin answer because there are more solutions for this problem. Here is another one (at least it works with ObjectContext API so it should work with DbContext API as well):
// Existing customer
var customer = new Customer() { Id = customerId };
// Another existing customer
var customer2 = new Customer() { Id = customerId2 };
var target = new Target { ID = oldTargetId };
// Make connection between target and old customer
target.Customer = customer;
// Attach target with old customer
context.Targets.Attach(target);
// Attach second customer
context.Customers.Attach(customer2);
// Set customer to a new value on attached object (it will delete old relation and add new one)
target.Customer = customer2;
// Change target's state to Modified
context.Entry(target).State = EntityState.Modified;
context.SaveChanges();
The problem here is internal state model and state validations inside EF. Entity in unchanged or modified state with mandatory relation (on many side) cannot have independent association in added state when there is no other in deleted state. Modified state for association is not allowed at all.
There is a lot of information to be found on this topic; on stackoverflow I found Ladislav Mrnka's insights particularly helpful. More on the subject can also be found here: NTier Improvements for Entity Framework and here What's new in Entity Framework 4?
In my project (Asp.Net Webforms) the user has the option to replace the Customer assigned to a Target object with a different (existing) Customer object. This transaction is performed by a FormView control bound to an ObjectDataSource. The ObjectDataSource communicates with the BusinessLogic layer of the project which in turns passes the transaction to a repository class for the Target object in the DataAccess layer. The Update method for the Target object in the repository class looks like this:
public void UpdateTarget(Target target, Target origTarget)
{
try
{
// It is not possible to handle updating one to many relationships (i.e. assign a
// different Customer to a Target) with "Independent Associations" in Code First.
// (It is possible when using "ForeignKey Associations" instead of "Independent
// Associations" but this brings about a different set of problems.)
// In order to update one to many relationships formed by "Independent Associations"
// it is necessary to resort to using the ObjectContext class (derived from an
// instance of DbContext) and 'manually' update the relationship between Target and Customer.
// Get ObjectContext from DbContext - ((IObjectContextAdapter)tgrDbContext).ObjectContext;
ObjectContext tgrObjectContext = TgrDbContext.TgrObjectContext(_tgrDbContext);
// Attach the original origTarget and update it with the current values contained in target
// This does NOT update changes that occurred in an "Independent Association"; if target
// has a different Customer assigned than origTarget this will go unrecognized
tgrObjectContext.AttachTo("Targets", origTarget);
tgrObjectContext.ApplyCurrentValues("Targets", target);
// This will take care of changes in an "Independent Association". A Customer has many
// Targets but any Target has exactly one Customer. Therefore the order of the two
// ChangeRelationshipState statements is important: Delete has to occur first, otherwise
// Target would have temporarily two Customers assigned.
tgrObjectContext.ObjectStateManager.ChangeRelationshipState(
origTarget,
origTarget.Customer,
o => o.Customer,
EntityState.Deleted);
tgrObjectContext.ObjectStateManager.ChangeRelationshipState(
origTarget,
target.Customer,
o => o.Customer,
EntityState.Added);
// Commit
tgrObjectContext.Refresh(RefreshMode.ClientWins, origTarget);
tgrObjectContext.SaveChanges();
}
catch (Exception)
{
throw;
}
}
This works for the Update method for the Target object. Remarkably, the procedure for inserting a new Target object is way easier. DbContext recognizes the Customer end of the independent association properly and commits the change to the database without further ado. The Insert method in the repository class looks like this:
public void InsertTarget(Target target)
{
try
{
_tgrDbContext.Targets.Add(target);
_tgrDbContext.SaveChanges();
}
catch (Exception)
{
throw;
}
}
Hopefully this will be useful to somebody dealing with a similar task. If you notice a problem with this approach described above, please let me know in your comments. Thanks!