Set to default instead delete cascade - c#

I am making code first entity framework database model, and I am struggeling with cascade delete. There are my simple classes:
public class User {
[Key()]
public int Id {get; set;}
public string Name {get; set;}
public int CampaignId {get; set;}
[ForeignKey("CampaignId")]
public virtual Campaign Campaign {get; set;}
}
public class Campaign {
[Key()]
public int Id {get; set;}
public string Description {get; set;}
public virtual List<User> Users {get; set;}
public Campaign() {
Users = new List<User>();
}
}
The basic idea is to assign one campaign to every user. When I delete campaign which is assigned by user:
internal static void DeleteCampaign(Campaign campaignToDelete) {
using (var context = new DatabaseContext()) {
context.Entry(campaignToDelete).State = EntityState.Deleted;
context.SaveChanges();
}
}
Users assigned to that campaign are deleted too. What I want is to not delete users, but assign them to first avaible campaign, or null. For some reason I cant do something like that:
internal static void DeleteCampaign(Campaign campaignToDelete) {
using (var context = new DatabaseContext()) {
for (int i = 0; i < campaignToDelete.Users.Count; i++) {
campaignToDelete.Users[i].Campaign = context.Campaigns.ElementAt(0);
}
context.Entry(campaignToDelete).State = System.Data.Entity.EntityState.Deleted;
context.SaveChanges();
}
}
Because I am getting error:
An unhandled exception of type 'System.ObjectDisposedException' occurred in EntityFramework.dll
Additional information: The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
So how can I avoid that?

Lazy loading should be enabled by default when using virtual.
This may be helpful:
How to disable cascade delete for link tables in EF code-first?

1) What kind of object do you pass to the DeleteCampaign method?
I think that is one of your entity framwork proxy models, which was queried by another database context.
You access in your for-loop the linked User object, which could not be loaded because the context doesn't exist anymore.
for (int i = 0; i < campaignToDelete.Users.Count; i++) {
Don't mix objects of different database contexts.
Better way is, that you pass the ID of the Campaign to the DeleteCampaign method and then you have to query the campaign in the new database context.
After that you can do all operations in one using statement (= the same database context).
static void DeleteCampaign(int idOfCampaignToDelete)
{
using (var context = new DatabaseContext())
{
var campaignToDelete = context.Campaigns.FirstOrDefault(c => c.Id == idOfCampaignToDelete);
for (int i = 0; i < campaignToDelete.Users.Count; i++)
{
campaignToDelete.Users[i].Campaign = context.Campaigns.ElementAt(0);
}
context.Entry(campaignToDelete).State = System.Data.Entity.EntityState.Deleted;
context.SaveChanges();
}
}
2) You cannot use ElementAt(0) in a LINQ to Entities query.
Use .FirstOrDefault().
campaignToDelete.Users[i].Campaign = context.Campaign.FirstOrDefault();
3) Make the CampaignId in your User class nullable. Otherwise you cannot have Users without Campaigns, because the Foreign Key would not be nullable.
public int? CampaignId {get; set;}
Now your code should work.

Related

Why is Entity Framework Core attempting to insert records into one of the tables from many to many relationships and NOT the join table?

Given the following set up where there are many Teams and there are many LeagueSessions. Each Team belongs to zero or more LeagueSessions but only ever one LeagueSession is active. LeagueSessions have many teams, and the teams will be repeated. Many-to-many relationship is established between Teams and LeagueSessions with a join table called TeamsSessions.
Team model looks like this:
public class Team
{
public string Id { get; set; }
public string Name { get; set; }
public League League { get; set; }
public string LeagueID { get; set; }
public bool Selected { get; set; }
public ICollection<Match> Matches { get; set; }
public virtual ICollection<TeamSession> TeamsSessions { get; set; }
}
Team model fluent api configuration:
`
public class TeamConfiguration
{
public TeamConfiguration(EntityTypeBuilder<Team> model)
{
// The data for this model will be generated inside ThePLeagueDataCore.DataBaseInitializer.DatabaseBaseInitializer.cs class
// When generating data for models in here, you have to provide it with an ID, and it became mildly problematic to consistently get
// a unique ID for all the teams. In ThePLeagueDataCore.DataBaseInitializer.DatabaseBaseInitializer.cs we can use dbContext to generate
// unique ids for us for each team.
model.HasOne(team => team.League)
.WithMany(league => league.Teams)
.HasForeignKey(team => team.LeagueID);
}
}
`
Each team belongs to a single League. League model looks like this:
`public class League
{
public string Id { get; set; }
public string Type { get; set; }
public string Name { get; set; }
public IEnumerable<Team> Teams { get; set; }
public bool Selected { get; set; }
public string SportTypeID { get; set; }
public SportType SportType { get; set; }
public IEnumerable<LeagueSessionSchedule> Sessions { get; set; }
}`
fluent API for the League:
`public LeagueConfiguration(EntityTypeBuilder<League> model)
{
model.HasOne(league => league.SportType)
.WithMany(sportType => sportType.Leagues)
.HasForeignKey(league => league.SportTypeID);
model.HasMany(league => league.Teams)
.WithOne(team => team.League)
.HasForeignKey(team => team.LeagueID);
model.HasData(leagues);
}`
SessionScheduleBase class looks like this:
public class SessionScheduleBase
{
public string LeagueID { get; set; }
public bool ByeWeeks { get; set; }
public long? NumberOfWeeks { get; set; }
public DateTime SessionStart { get; set; }
public DateTime SessionEnd { get; set; }
public ICollection<TeamSession> TeamsSessions { get; set; } = new Collection<TeamSession>();
public ICollection<GameDay> GamesDays { get; set; } = new Collection<GameDay>();
}
Note: LeagueSessionSchedule inherits from SessionScheduleBase
The TeamSession model looks like this:
`public class TeamSession
{
public string Id { get; set; }
public string TeamId { get; set; }
public Team Team { get; set; }
public string LeagueSessionScheduleId { get; set; }
public LeagueSessionSchedule LeagueSessionSchedule { get; set; }
}`
I then configure the relationship with the fluent API like this:
`public TeamSessionConfiguration(EntityTypeBuilder<TeamSession> model)
{
model.HasKey(ts => new { ts.TeamId, ts.LeagueSessionScheduleId });
model.HasOne(ts => ts.Team)
.WithMany(t => t.TeamsSessions)
.HasForeignKey(ts => ts.TeamId);
model.HasOne(ts => ts.LeagueSessionSchedule)
.WithMany(s => s.TeamsSessions)
.HasForeignKey(ts => ts.LeagueSessionScheduleId);
}`
The problem arises whenever I attempt to insert a new LeagueSessionSchedule. The way I am adding a new TeamSession object onto the new LeagueSessionSchedule is like this:
`foreach (TeamSessionViewModel teamSession in newSchedule.TeamsSessions)
{
Team team = await this._teamRepository.GetByIdAsync(teamSession.TeamId, ct);
if(team != null)
{
TeamSession newTeamSession = new TeamSession()
{
Team = team,
LeagueSessionSchedule = leagueSessionSchedule
};
leagueSessionSchedule.TeamsSessions.Add(newTeamSession);
}
}`
Saving the new LeagueSessionSchedule code:
public async Task<LeagueSessionSchedule> AddScheduleAsync(LeagueSessionSchedule newLeagueSessionSchedule, CancellationToken ct = default)
{
this._dbContext.LeagueSessions.Add(newLeagueSessionSchedule);
await this._dbContext.SaveChangesAsync(ct);
return newLeagueSessionSchedule;
}
Saving the new LeagueSessionSchedule object throws an error by Entity Framework Core that it cannot INSERT a duplicate primary key value into the dbo.Teams table. I have no idea why its attempting to add to dbo.Teams table and not into TeamsSessions table.
ERROR:
INSERT INTO [LeagueSessions] ([Id], [Active], [ByeWeeks], [LeagueID], [NumberOfWeeks], [SessionEnd], [SessionStart])
VALUES (#p0, #p1, #p2, #p3, #p4, #p5, #p6);
INSERT INTO [Teams] ([Id], [Discriminator], [LeagueID], [Name], [Selected])
VALUES (#p7, #p8, #p9, #p10, #p11),
(#p12, #p13, #p14, #p15, #p16),
(#p17, #p18, #p19, #p20, #p21),
(#p22, #p23, #p24, #p25, #p26),
(#p27, #p28, #p29, #p30, #p31),
(#p32, #p33, #p34, #p35, #p36),
(#p37, #p38, #p39, #p40, #p41),
(#p42, #p43, #p44, #p45, #p46);
System.Data.SqlClient.SqlException (0x80131904): Violation of PRIMARY KEY constraint 'PK_Teams'. Cannot insert duplicate key in object 'dbo.Teams'. The duplicate key value is (217e2e11-0603-4239-aab5-9e2f1d3ebc2c).
My goal is to create a new LeagueSessionSchedule object. Along with the creation of this object, I also have to create a new TeamSession entry to the join table (or not if join table is not necessary) to then be able to pick any given team and see what session it is currently a part of.
My entire PublishSchedule method is the following:
`
public async Task<bool> PublishSessionsSchedulesAsync(List<LeagueSessionScheduleViewModel> newLeagueSessionsSchedules, CancellationToken ct = default(CancellationToken))
{
List<LeagueSessionSchedule> leagueSessionOperations = new List<LeagueSessionSchedule>();
foreach (LeagueSessionScheduleViewModel newSchedule in newLeagueSessionsSchedules)
{
LeagueSessionSchedule leagueSessionSchedule = new LeagueSessionSchedule()
{
Active = newSchedule.Active,
LeagueID = newSchedule.LeagueID,
ByeWeeks = newSchedule.ByeWeeks,
NumberOfWeeks = newSchedule.NumberOfWeeks,
SessionStart = newSchedule.SessionStart,
SessionEnd = newSchedule.SessionEnd
};
// leagueSessionSchedule = await this._sessionScheduleRepository.AddScheduleAsync(leagueSessionSchedule, ct);
// create game day entry for all configured game days
foreach (GameDayViewModel gameDay in newSchedule.GamesDays)
{
GameDay newGameDay = new GameDay()
{
GamesDay = gameDay.GamesDay
};
// leagueSessionSchedule.GamesDays.Add(newGameDay);
// create game time entry for every game day
foreach (GameTimeViewModel gameTime in gameDay.GamesTimes)
{
GameTime newGameTime = new GameTime()
{
GamesTime = DateTimeOffset.FromUnixTimeSeconds(gameTime.GamesTime).DateTime.ToLocalTime(),
// GameDayId = newGameDay.Id
};
// newGameTime = await this._sessionScheduleRepository.AddGameTimeAsync(newGameTime, ct);
newGameDay.GamesTimes.Add(newGameTime);
}
leagueSessionSchedule.GamesDays.Add(newGameDay);
}
// update teams sessions
foreach (TeamSessionViewModel teamSession in newSchedule.TeamsSessions)
{
// retrieve the team with the corresponding id
Team team = await this._teamRepository.GetByIdAsync(teamSession.TeamId, ct);
if(team != null)
{
TeamSession newTeamSession = new TeamSession()
{
Team = team,
LeagueSessionSchedule = leagueSessionSchedule
};
leagueSessionSchedule.TeamsSessions.Add(newTeamSession);
}
}
// update matches for this session
foreach (MatchViewModel match in newSchedule.Matches)
{
Match newMatch = new Match()
{
DateTime = match.DateTime,
HomeTeamId = match.HomeTeam.Id,
AwayTeamId = match.AwayTeam.Id,
LeagueID = match.LeagueID
};
leagueSessionSchedule.Matches.Add(newMatch);
}
try
{
leagueSessionOperations.Add(await this._sessionScheduleRepository.AddScheduleAsync(leagueSessionSchedule, ct));
}
catch(Exception ex)
{
}
}
// ensure all leagueSessionOperations did not return any null values
return leagueSessionOperations.All(op => op != null);
}
`
This is not a many-to-many relationship.
It is two separate one-to-many relationships, which happen to refer to the same table on one end of the relationship.
While it is true that on the database level, both use cases are represented by three tables, i.e. Foo 1->* FooBar *<-1 Bar, these two cases are treated differently by Entity Framework's automated behavior - and this is very important.
EF only handles the cross table for you if it is a direct many-to-many, e.g.
public class Foo
{
public virtual ICollection<Bar> Bars { get; set; }
}
public class Bar
{
public virtual ICollection<Foo> Foos { get; set; }
}
EF handles the cross table behind the scenes, and you are never made aware of the existence of the cross table (from the code perspective).
Importantly, EF Core does not yet support implicit cross tables! There is currently no way to do this in EF Core, but even if there were, you're not using it anyway, so the answer to your problem remains the same regardless of whether you're using EF or EF Core.
However, you have defined your own cross table. While this is still representative of a many-to-many relationship in database terms, it has ceased to be a many-to-many relationship as far as EF is concerned, and any documentation you find on EF's many-to-many relationships no longer applies to your scenario.
Unattached but indirectly added objects are assumed to be new.
By "indirectly added", I mean you that it was added to the context as part of another entity (which you directly added to the context). In the following example, foo is directly added and bar is indirectly added:
var foo = new Foo();
var bar = new Bar();
foo.Bar = bar;
context.Foos.Add(foo); // directly adding foo
// ... but not bar
context.SaveChanges();
When you add (and commit) a new entity to the context, EF adds it for you. However, EF also looks at any related entities that the first entity contains. During the commit in the above example, EF will look at both the foo and bar entities and will handle them accordingly. EF is smart enough to realize that you want bar to be stored in the database since you put it inside the foo object and you explicitly asked EF to add foo to the database.
It is important to realize that you've told EF that foo should be created (since you called Add(), which implies a new item), but you never told EF what it should do with bar. It's unclear (to EF) what you expect EF to do with this, and thus EF is left guessing at what to do.
If you never explained to EF whether bar already exists or not, Entity Framework defaults to assuming it needs to create this entity in the database.
Saving the new LeagueSessionSchedule object throws an error by Entity Framework Core that it cannot INSERT a duplicate primary key value into the dbo.Teams table. I have no idea why its attempting to add to dbo.Teams table
Knowing what you now know, the error becomes clearer. EF is trying to add this team (which was the bar object in my example) because it has no information on this team object and what its state in the database is.
There are a few solutions here.
1. Use the FK property instead of the navigational property
This is my preferred solution because it leaves no room for error. If the team ID does not yet exist, you get an error. At no point will EF try to create a team, since it doesn't even know the team's data, it only knows the (alleged) ID you're trying to create a relationship with.
Note: I am omitting LeagueSessionSchedule as it is unrelated to the current error - but it's essentially the same behavior for both Team and LeagueSessionSchedule.
TeamSession newTeamSession = new TeamSession()
{
TeamId = team.Id
};
By using the FK property instead of the nav prop, you are informing EF that this is an existing team - and therefore EF no longer tries to (re)create this team.
2. Ensure that the team is tracked by the current context
Note: I am omitting LeagueSessionSchedule as it is unrelated to the current error - but it's essentially the same behavior for both Team and LeagueSessionSchedule.
context.Teams.Attach(team);
TeamSession newTeamSession = new TeamSession()
{
Team = team
};
By attaching the object to the context, you are informing it of its existence. The default state of a newly attached entity is Unchanged, meaning "this already exists in the database and has not been changed - so you don't need to update it when we commit the context".
If you have actually made changes to your team that you want to be updated during commit, you should instead use:
context.Entry(team).State = EntityState.Modified;
Entry() inherently also attaches the entity, and by setting its state to Modified you ensure that the new values will be committed to the database when you call SaveChanges().
Note that I prefer solution 1 over solution 2 because it's foolproof and much less likely to lead to unexpected behavior or runtime exceptions.
String primary keys are undesirable
I'm not going to say that it doesn't work, but strings cannot be autogenerated by Entity Framework, making them undesirable as the type of your entity's PK. You will need to manually set your entity PK values.
Like I said, it's not impossible, but your code shows that you're not explicitly setting PK values:
if(team != null)
{
TeamSession newTeamSession = new TeamSession()
{
Team = team,
LeagueSessionSchedule = leagueSessionSchedule
};
leagueSessionSchedule.TeamsSessions.Add(newTeamSession);
}
If you want your PK's to be automatically generated, use an appropriate type. int and Guid are by far the most commonly used types for this.
Otherwise, you're going to have to start setting your own PK values, because if you don't (and the Id value thus defaults to null), your code is going to fail when you add a second TeamSession object using the above code (even though you're doing everything else correctly), since PK null is already taken by the first entity you added to the table.

Entity Framework Lazy Loading Unexpected Property Population on Extracted Data

See following simplified example:
Student Class:
public class Student
{
public int StudentId { get; set; }
public string StudentName { get; set; }
public Grade Grade { get; set; }
}
Grade Class:
public class Grade
{
public int GradeId { get; set; }
public string GradeName { get; set; }
public ICollection<Student> Students { get; set; }
}
Context class:
public class SchoolContext : DbContext
{
public SchoolContext() : base()
{
Configuration.LazyLoadingEnabled = true;
}
public DbSet<Student> Students { get; set; }
public DbSet<Grade> Grades { get; set; }
}
Program:
static void Main(string[] args)
{
using (var ctx = new SchoolContext())
{
Grade[] grades = ctx.Grades.ToArray();
Console.WriteLine(grades[0].Students == null); // True - As expected
var students = ctx.Students.ToArray();
Console.WriteLine(grades[0].Students == null); // False - ? Did not expect that
}
Console.Read();
}
The following happens:
Lazy loading was enabled
List of Grades was saved into an array
As expected, Students navigation property of grade objects was null
Made a separate query to get Students
EF somehow filled up the Students navigation property of the array in memory.
This could end up in very expensive payloads to clients if not used with care.
Can anyone explain why and how did the navigation properties got populated in the array?
The reason the grades[0].Students is loaded after doing a query to get students from your database with ctx.Students.ToArray(); is that you DbContext is tracking changes.
This is explained in Entity Framework docs:
Tracking behavior controls whether or not Entity Framework Core will keep information about an entity instance in its change tracker. If an entity is tracked, any changes detected in the entity will be persisted to the database during SaveChanges(). Entity Framework Core will also fix-up navigation properties between entities that are obtained from a tracking query and entities that were previously loaded into the DbContext instance.
This is the EF Core docs, but this also applies to EF6 for .NET Framework.
If you want to disable this behavior, you may load your entities as no tracking:
ctx.Grades.AsNoTracking().ToArray();
...you could also disable it by default (e.g inside the DbContext constructor), the same way you do for lazy load.
Another way you could do that is to manually detach an object from the context.
Then if you ever intended to make any changes and persist it to database, you should reattach your entity after querying students, and before making your changes:
using (var ctx = new SchoolContext())
{
Grade[] grades = ctx.Grades.ToArray();
Grade firstGrade = grades[0];
Console.WriteLine(firstGrade.Students == null); // True - as expected
ctx.Grades.Detach(firstGrade); // stop tracking changes for this entity
var students = ctx.Students.ToArray();
Console.WriteLine(firstGrade.Students == null); // True - still null
// Let's reattach so we can track changes and save to database
ctx.Grades.Attach(firstGrade);
firstGrade.GradeName = "Some new value"; // will be persisted, as this is being tracked again
ctx.SaveChanges();
}
Also, it's worth mentioning with lazy load enabled, accessing grades[0].Students the first time should make EF load that navigation property if it was not loaded yet (which is precisely its purpose), however it seems this is not happening because your navigation property is not virtual.

Circular reference issue in webapi2

Scenario:
An Intern can learn multiple technologies
db design
ef view
Result
controller code:
private InternEntities db = new InternEntities();
// GET: api/Interns
public IQueryable<Intern> GetInterns()
{
return db.Interns;
}
What am i doing wrong here?
This is an expected error, and the reason is because your types reference each other like an Infinity Mirror. In order to solve the problem, you have several options.
1- You can develop a ViewModel and then serialize that one:
public class InternViewModel{
public int Id {get; set;}
public String Name {get; set;}
public List<String> Tehcnologies {get; set;}
}
2- You can select the properties that you need when returning the entity in your actions:
public async Task<List<Technology>> Get() {
var data = dbContext.Set<Technology>().Select(x=> new Technology{
Id = x.Id,
Name = x.Name,
Intern= new Intern {
Id = x.Technology.Id,
Name = x.Technology.Name,
Technologies = null
}
});
return await data.ToListAsync();
}
3- Load only the what you need which is known as Explicit Loading.

Avoid duplicating disconnected entity

Here is what I have :
Domain model :
public class MyDomainEntity1
{
public string Name { get; set; }
public string Text { set; set; }
}
public class MyDomainEntity2
{
public Entity1 {get; set;}
public string Entity1Name {get; set;}
public string SomeIrrelevantProps {get; set;}
}
Context :
protected override OnModelBuilding(modelbuilder)
{
modelbuilder.Entity<MyDomainEntity1>().HasKey(x=> x.Name);
modelbuilder.Entity<MyDomainEntity2>().HasRequired(x=> x.Entity1).WithOptional().HasForeignKey(x=> x.Entity1Name);
}
Seeding code :
context.Entities1DbSet.AddOrUpdate(x=>x.Name, new { Name = "ReferentialName1" });
context.Entities1DbSet.AddOrUpdate(x=>x.Name, new { Name = "ReferentialName2" });
context.SaveChanges(); // up to this point Working fine even when called several time.
context.Entities2DbSet.Add( new MyDomainEntity2 { Entity1 = new MyDomainEntity1 { Name = "ReferentialName1"}, Entity1Name = "ReferentialName1" });
context.SaveChanges() // throws the following exception :
Violation of PRIMARY KEY constraint 'PK_dbo.MyDomainEntity1'. Cannot insert duplicate key in object 'dbo.MyDomainEntity1'. The duplicate key value is (ReferentialName1).
The statement has been terminated.
For some reason EF tries to add a new MyDomainEntity1 instead of using the entry already existing in the database. I used this pattern without ANY problem up to now. It seems that EF was automatically attaching the new entities I was adding thus linking them to the already existing row in the DB.
However for some reason it stopped working and I'm getting the error I have now. I use this pattern a lot in my domain model to refer to referential data by simply instantiating a class. I can't see what I've changed to break it.
Any idea ?
Please note that calling DbSet.Attach() is not an option for me. The instantiations of MyDomainEntity1 are usually done in my domain model project where I don't have access to the DB.

Entity Framework won't detect changes of navigation properties

I'm having trouble with detecting changes of a navigation property:
My testing model looks like this:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Address Address { get; set; }
}
public class Address
{
public int Id { get; set; }
public string Name { get; set; }
}
I've created and saved an object of type Person with both Name and Address properties assigned. My problem is that if I fetch the Person object back from the database and I change the Address property (ex. to null) then EF doesn't detect the change!
My code is this:
using (var ctx = new EFContext())
{
Person p = ctx.People.First();
// p.Address IS NOT NULL!
p.Address = null;
var entry = ctx.Entry(p);
}
Why is entry.State Unchanged ?
Edit: If I call SaveChanges, the record is saved correctly (the Address become null)!
Edit 2: I've created the foreign key property as billy suggested. If I inspect the Person object in Visual Studio, the State is Modified. If I don't stop with the debugger inspecting the object's values, the state is Unchanged!
Edit 3: Loading the Person object using ctx.People.Include(x => x.Address).First(); solves the problem. Is there a way to avoid calling Include and continue to modify the Address property instead of the AddressId one?
First of all: You MUST follow #billy's advice to use Include. Your remark "p.Address IS NOT NULL!" is only true because you are watching p.Address in the debugger and thereby triggering lazy loading in the debugger, so the change of setting the address to null is detected. In release mode or when you don't inspect the properties in the debugger your code wouldn't work and no changes would be saved.
So, the answer to your Edit 3 is: No.
Second: var entry = ctx.Entry(p) only returns entity states and you didn't change an entity state but instead a relationship state, or more precisely you deleted a relationship. You can't inspect relationship states with the DbContext API but only with the ObjectContext API:
Person p = ctx.People.Include(x => x.Address).First();
p.Address = null;
var objCtx = ((IObjectContextAdapter)ctx).ObjectContext;
var objentr = objCtx.ObjectStateManager.GetObjectStateEntries(EntityState.Deleted);
objentr will have an entry of type RelationshipEntry now:
EF will consider this relationship entry together with entity state entries when you call SaveChanges() and delete the relationship, i.e. set the Address foreign key column of the Person in the database to NULL.
About Edit 2: Changing a foreign key property (which is a scalar property in your model) is a change of the entity itself, so the entity state will be Modified in this case.
You need to include the Address nav. prop. in your query, otherwise EF won't consider changes to it when you save :
using (var ctx = new EFContext())
{
Person p = ctx.People.Include(x => x.Address).First();
//p.Address IS NOT NULL!
p.Address = null;
var entry = ctx.Entry(p);
}
You could also use foreign keys in your model, which I like very much :
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Address Address { get; set; }
public int? AddressId {get; set;}
}
...
using (var ctx = new EFContext())
{
Person p = ctx.People.First();
p.AddressId = null;
var entry = ctx.Entry(p);
}
In my application, before a reload is requested or the user leaves the item/view, I perform some checks to make sure there are no unsaved changes.
This is basically running off the currently accepted answer, but I wanted to provide an implementation and bring to the attention that you must call Context.ChangeTracker.DetectChanges() before the ObjectContext.ObjectStateManager can pick up relationship changes! I spent quite a bit of time debugging this, silly!
_EagleContext.ChangeTracker.DetectChanges();
var objectContext = ((IObjectContextAdapter)_EagleContext).ObjectContext;
var changedEntities = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified);
if (_EagleContext.ChangeTracker.Entries().Any(e => e.State == EntityState.Modified)
|| changedEntities.Count() != 0)
{
var dialogResult = MessageBox.Show("There are changes to save, are you sure you want to reload?", "Warning", MessageBoxButton.YesNo);
if (dialogResult == MessageBoxResult.No)
{
return;
}
}
// Continue with reloading...

Categories

Resources