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.
i have maintened a big web system built some years ago and it is already in production (i am saying this to to emphasize the complexity of tinkering with the system structure).
Till now this system has been working with physical exclusion of database records, however now there is a necessity to change it to logical exclusion (updating the status (to "D") of a register in table).
My entities are mapped by FluentApi.
My properties in context are built using DbSet, example:
DbSet Person {get;set;}
I would like to know a way to only fetch records in my entity with status != "D" straight from entity mapping or through an overall filter that always executes when i call my entity in context.
I have tried a solution like suggested here:
http://patrickdesjardins.com/blog/using-a-filtereddbset-with-entity-framework-to-have-dynamic-filtering
It actually it works fine to fetch the data i want however, i cannot use the methods to change the record like "AddOrUpdate" because when i use that solution my property needs to be a IDbSet and not a DbSet.
Example:
To use the solution suggested in the above link the property must be changed from this:
DbSet Person { get; set; }
to this:
IDbSet Person { get; set; }
Sample code:
// My Entity
public class Person
{
public int Id {get; set;}
public string Name {get; set;}
public string Status {get; set;}
public ICollection<Address> PersonAddress {get; set;}
}
// My Context example
public class MyContext : DbContext
{
public MyContext(System.Data.Common.DbConnection conn, bool contextOwnsConnection) : base(conn, contextOwnsConnection)
{
ConnectionString = conn.ConnectionString;
Database.SetInitializer<MyContext>(null);
SetDatabaseSettings(); //NLS and regional configurations
//Here i am calling the method built in the link above
this.Person = new FilteredDbSet<Person>(this, a => !a.Status.Equals("D"));
}
// This is now a IDbSet type
public IDbSet<Person> Persons { get; set; }
// The others are DbSet type
public DbSet<Address> Adressess { get; set; }
}
// Any use of context
public class PersonsClass{
private readonly MyContext context;
public PersonsClass(MyContext context)
{
this.contexto = context;
}
// Considering the filter applied in MyContext constructor method
// this method returns all "person" with status property != D (OK)
public List<Person> ListAllPersons()
{
return context.Person.toList();
}
// When try to perform add or update method, the system throws the following exception:
// "Unable to call public, instance method AddOrUpdate on derived IDbSet type 'Context.FilteredDbSet`1[Entities.Person]'. Method not found."
// I believe that this is happening because "AddOrUpdate" method belongs to DbSet class instead of IDbSet interface.
public void UpdatePerson(Person newPerson)
{
context.Person.AddOrUpdate(newPerson);
}
}
So, when i try to change a record of this entity the exception: "Unable to call public, instance method AddOrUpdate on derived IDbSet type 'Context.FilteredDbSet`1[Entities.Person]'. Method not found." is thrown.
Any tips about how to go through it?
I use Entity Framework as ORM in my project. Let's suppose I use Code-First pattern and I have two models. Such as
internal class First
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public string Name { get; set; }
}
internal class Second
{
public int Id { get; set; }
public First ForeignKeyEntity { get; set; }
// other members
}
And here is code populating database:
List<Second> res = GetData();
using (var ctx = new StatisticContext())
{
foreach (var item in res)
{
ctx.Seconds.Add(item);
}
ctx.SaveChanges();
}
As you can see each instance of class Second has instance of class First in its member ForeignKeyEntity. Obviously some instances of First can be duplicated in res. And when I run this code I get DbUpdateException in ctx.SaveChanges() with inner exception that has the following message:
Violation of PRIMARY KEY constraint 'PK_dbo.First'.
Cannot insert duplicate key in object 'dbo.First'.
The duplicate key value is (29459). The statement has been terminated.
I can not to insert duplicated rows but I don't want to insert duplicates, I would like to insert row only if it doesn't exist. How to solve this problem? How to insert foreign key object only if doesn'tt exist?
The normal way of doing things would be to do a read first with item to see if it exists. If it does then you need to use ctx.Seconds.Update(item);
If your items are already on the context, then you can check the state. it will be either State.Modified or State.Added.
Whats in GetData()
I have an application with 4 layers:
-Core (Models)
-Repository (EF DbContext actions)
-Service (Business logic)
-Web (MVC)
I'm trying to update an object with a 1:1 relationship with EF using the following method:
public async Task<bool> UpdateProductTicketing(ProductTicketing ticketing)
{
var product = await GetProductByIdAsync(ticketing.ProductId);
// Validation removed for simplicity
// 'ticketing' passed validation so let's
// just replace it with the existing record.
product.Ticketing = ticketing;
_repo.ProductRepository.Update(product);
return await _repo.SaveAsync();
}
This works for an initial insert, but it doesn't work as I'd expect when I'm updating the record:
A first chance exception of type 'System.Data.Entity.Infrastructure.DbUpdateException' occurred...
The actual error message is:
Violation of PRIMARY KEY constraint 'PK_dbo.ProductTicketing'. Cannot insert duplicate key in object 'dbo.ProductTicketing'. The statement has been terminated.
Obviously the PK and FK "ProductId" doesn't change - so why does EF try to drop and insert my record instead of just updating it, and why does it fail?
But more importantly - how can I prevent this. I know I can manually map the object values and then update it - that works but it's tedious mapping two identical objects together and doesn't feel correct.
My repository for retrieving the Product object is in my Repository layer, while the method above is in my Service layer.
This is how I'm currently resolving this - and it looks as dirty as it feels:
public async Task<bool> UpdateProductTicketing(ProductTicketing ticketing)
{
var product = await GetProductByIdAsync(ticketing.ProductId);
// Validation removed for simplicity
if (product.Ticketing == null)
{
product.Ticketing = ticketing;
}
else
{
product.Ticketing.AllowEventBooking = ticketing.AllowEventBooking;
// Doing the same for all other properties etc
// etc
// etc
}
_repo.ProductRepository.Update(product);
return await _repo.SaveAsync();
}
How can I achieve this without doing all this horrible mapping an object to an identical object?
Edit
Here are the two models referred to above:
[Table(#"Products")]
public class Product
{
[Key]
public int Id { get; set; }
public virtual ProductTicketing Ticketing { get; set; }
// Removed others for clarity
[Timestamp]
public byte[] RowVersion { get; set; }
}
[Table(#"ProductTicketing")]
public class ProductTicketing
{
[Key, ForeignKey("Product")]
public int ProductId { get; set; }
public bool AllowEventBooking { get; set; }
// Removed others for clarity
public virtual Product Product { get; set; }
}
It's also probably worth noting that the "ProductTicketing" object I'm passing into the UpdateProductTicketing method is a new object created from values in my controller - but the ID is the same so I assume it should work.
I think I see the problem now - when you do product.Ticketing = ticketing;, EF treats this as a new insert.
To avoid this, you can do one of these things:
Continue using the workaround (which is not a wokaround actually but just the way EF expects you to tell when to insert vs. when to update).
Now this depends on rest of your code and design, but instead of fetching the product, you can fetch the ticket and update its properties. Of course, this means that if the ticketing is not found, you need to insert it which then kinda looks like what you're already doing with UpdateProductTicketing.
Use the InsertOrUpdate pattern (I made some assumptions about your code but hopefully it gives you the idea - the main thing here is the InsertOrUpdate method):
public class ProductRepository : IRepository
{
private SomeContext context;
public void InsertOrUpdate(ProductTicketing ticketing)
{
context.Entry(ticketing).State = ticketing.ProductId == 0 ?
EntityState.Added :
EntityState.Modified;
context.SaveChanges();
}
}
// And a generic version
public void InsertOrUpdate<T>(T entity) where T : class
{
if (context.Entry(entity).State == EntityState.Detached)
context.Set<T>().Add(entity);
context.SaveChanges();
}
You are getting that error because ef thinks that the ProductTicket is a new entity and is trying to insert the entity into the db. I don't know about the _repo.ProductRepository.Update(product) call but how about you attach the ProductTicket to the context and set the entity state to modified
I'm working on a project using entity framework and code first. Here are my shortened classes
public class BenefitsForm : IAnnualForm, IAuditable
{
public BenefitsUser UserInfo { get; set; }
public CoverageLevel Medical { get; set; }
}
public class MedicalPlan : IHealthPlan
{
public int Id { get; set; }
public virtual IList<CoverageLevel> CoverageLevels { get; set; }
}
public class CoverageLevel
{
public int Id { get; set; }
public virtual MedicalPlan MedicalPlan { get; set; }
}
There are 4 coverage levels in each MedicalPlan. I already have my coverage levels and medical plans made in the database. However, when I create my BenefitsForm, it creates duplicate entries for the classes (I already have them in the database). How can I prevent it from doing this? Here is a small code snippet to show how it happens.
BenefitsForm form = new BenefitsForm() { UserInfo = new BenefitsUser() };
using(var repo = new CoverageLevelRepository())
{
form.Medical = repo.Retrieve(new NumericKey(formId))); //this retrieves the coveragelevel with the id I want.
}
formRepository.Create(form); // this creates a duplicate med plan and 4 duplicate coverage levels.
formRepository.SaveChanges();
I think because you're retrieving the Medical field from a different context than the one you're saving in -- repo vs. formRepository; formRepository isn't tracking the object created by repo, so it assumes that it's a new object, and thus creates a duplicate entry. For performance reasons, I don't believe Entity Framework will go and insert existence checks for you -- tracking is handled internally by the object context itself; each object is bound to a single object context. Try using the same repository to retrieve the MedicalPlan and write back the new BenefitsForm, and you shouldn't have duplicates.
BenefitsForm form = new BenefitsForm() { UserInfo = new BenefitsUser() };
using(var repo = new Repository())
{
form.Medical = repo.Retrieve(new NumericKey(formId))); //this retrieves the coveragelevel with the id I want.
repo.Create(form); // this creates a duplicate med plan and 4 duplicate coverage levels.
repo.SaveChanges();
}
Instead of formRepository.SaveChanges() please try with to use the SaveChanges with the SaveOptions Enum
Example:
ObjectContext.SaveChanges(
System.Data.Objects.SaveOptions.DetectChangesBeforeSave
);
Please see link for more information http://msdn.microsoft.com/en-us/library/dd395500.aspx
It's possible that using IList<...> instead of ICollection<...> for your one-to-many association is causing it to malfunction. Try changing that. Other than that, it may be code within your CoverageLevelRepository that you did not post. For more info on associations, see this guide