I'm building a feedback functionality. The feedback is supposed to have multiple categories and it should be possible to find feedback based on a category so there is a many to many relationship.
I've set up the following code for this, its designed as code first.
The feeback item:
public class FeedbackItem
{
public FeedbackItem()
{
}
[Key]
public long Id { get; set; }
public virtual ICollection<FeedbackCategory> Categorys { get; set; }
//public
public string Content { get; set; }
public bool Notify { get; set; }
public string SubscriptionUserName { get; set; }
public string SubscriptionUserEmail { get; set; }
public long SubscriptionId { get; set; }
}
The feedback category:
public class FeedbackCategory
{
[Key]
public int Id { get; set; }
public string Value { get; set; }
public virtual ICollection<FeedbackItem> Feedbacks { get; set; }
}
The Database Context:
public class FeedbackContext : DbContext, IFeedbackContext
{
public FeedbackContext() : base("DefaultConnection")
{
//Database.SetInitializer<FeedbackContext>(new FeedbackContextDbInitializer());
}
public DbSet<FeedbackItem> FeedbackItems { get; set; }
public DbSet<FeedbackCategory> Categories { get; set; }
}
And the Initializer
class FeedbackContextDbInitializer : DropCreateDatabaseAlways<FeedbackContext>
{
protected override void Seed(FeedbackContext context)
{
IList<FeedbackCategory> categories = new List<FeedbackCategory>()
{
new FeedbackCategory() { Value = "Android" },
new FeedbackCategory() { Value = "API" }
};
foreach (var feedbackCategory in categories)
{
context.Categories.Add(feedbackCategory);
}
base.Seed(context);
}
}
The code above generates three tables when ran. These being FeedbackCategories, FeedbackCategoryFeedbackItems and FeedbackItems
The table FeedbackCategories is seeded with some already existing categories. The trouble comes when I try to create a new FeedbackItem that has one or more categories.
The Json i provide is the following:
{
"categorys": [
{
"$id": "1",
"Feedbacks": [],
"Id": 1,
"Value": "Android"
}
],
"subscriptionUserName": "name",
"subscriptionUserEmail": "my#email.com",
"content": "this is a feedback item",
"notify": false,
"subscriptionId": 2
}
This is converted into a FeedbackItem and handled by the following code
public class FeedbackSqlRepository : IFeedbackSqlRepository
{
public int Create(FeedbackItem feedback)
{
if (feedback == null)
{
throw new ArgumentNullException("feedback", "FeedbackItem cannot be empty.");
}
using (var context = new FeedbackContext())
{
context.FeedbackItems.Add(feedback);
return context.SaveChanges();
}
}
}
The thing that happens here is that EF creates a new FeedbackItem, a new FeedbackCategory an maps the created feedback item to the newly created feedback category in the FeedbackCategoryFeedbackItems table.
this is not the working i want
I want the following:
Create a new FeedbackItem and reverence an existing FeedbackCategory in the FeedbackCategoryFeedbackItems table. My knowledge of EF is too little to understand what's going wrong here and what to do to get the preferred working.
========
Fixed the issue with the following code inside the Create method from the FeedbackSqlRepository:
foreach (FeedbackCategory feedbackCategory in feedback.Categories)
{
context.Entry(feedbackCategory).State = EntityState.Unchanged;
}
context.FeedbackItems.Add(feedback);
return context.SaveChanges();
Entity Framework will not examine entity contents and determine for you if they are new or added.
DbSet.Add() causes ALL entities in an object graph to be marked as added and to generate inserts when you call SaveChanges().
DbSet.Attach() leaves all entities marked as Unmodified.
If some of your entities are new, some are modified and some are just references then you should use either Add() or Attach() and then manually set entity states where necessary before calling SaveChanges().
DbContext.Entry(entity).State = EntityState.Unmodified
Related
I have following sample JSON (simplified):
{
"programmes":[
{
"programmeID":"163",
"title":"Programme 1",
"color":"#ff5f57",
"moderators":[
{
"moderatorID":"27",
"name":"Moderator 1",
}
]
},
{
"programmeID":"153",
"title":"Programme 2",
"color":"#ff5f57",
"moderators":[
{
"moderatorID":"27",
"name":"Moderator 1",
}
]
},
]
}
I want to convert into following objects:
public class Programme
{
[Key]
public string programmeID { get; set; }
public string title { get; set; }
//Navigational Property
public virtual ICollection<Moderator> moderators { get; set; }
}
public class Moderator
{
[Key]
public string moderatorID { get; set; }
public string name { get; set; }
//Navigational Property
public virtual ICollection<Programme> programmes { get; set; }
}
Everything is fine, objects looks great, populated correctly, but when trying to save into db a get this error:
The instance of entity type 'Moderator' cannot be tracked because
another instance with the key value '{moderatorID: 27}' is already
being tracked. When attaching existing entities, ensure that only one
entity instance with a given key value is attached.
This error happens when calling .Add with the list of Programmes:
List<Programme> programmes;
programmes = JsonSerializer.Deserialize<List<Programme>>(JSONcontent);
//... db init
db.AddRange(programmes);
I understand that there is a duplicity for Moderator (ID=27), but I think that it's quite a normal pattern in JSON. I can do some workaround, but wondering if there is any best practice how to handle this in EF directly?
you can not add children you are created from deserialization as an existed ones. One of the solutions is finding the existed children at first
List<Programme> programmes = JsonSerializer.Deserialize<List<Programme>>(JSONcontent);
foreach (var program in programmes)
{
var moderators=new List<Moderator>();
foreach (var moderator in program.moderators)
{
var existingModerator=db.Moderators.FirstOrDefault(i=>
i.moderatorId=moderator.moderatorId);
moderators.Add(existingModerator);
}
programm.moderators=moderators;
}
db.AddRange(programmes);
but I usually prefer to create an explicit class for many to many relations. It will make all your code much more simple.
I have been looking to log changes on entities in a disconnected scenario.
Original Problem: I call DbContext.Update(Entity) on an updated complex entity and even though nothing has changed, everything is marked as Changed by the ChangeTracker and my concurrency RowVersion column counts up.
POST User with no changes
"userSubPermissions": [],
"fidAdresseNavigation": {
"idAdresse": 1,
"fidPlzOrt": 3,
"strasse": "Gerold Str.",
"hausnr": "45",
"rowVersion": 10,
"isDeleted": 0,
"fidPlzOrtNavigation": {
"idPlzOrt": 3,
"plz": "52062",
"ort": "Aachen",
"rowVersion": 9,
"isDeleted": 0
}
},
"idUser": 35,
"fidAnrede": null,
"fidAdresse": 1,
"fidAspnetuser": "a7ab78be-859f-4735-acd1-f06cd832be7e",
"vorname": "Max",
"nachmname": "Leckermann",
"eMail": "kunde#leasing.de",
"rowVersion": 11,
"isDeleted": 0
POST Returned user
"userSubPermissions": [],
"fidAdresseNavigation": {
"idAdresse": 1,
"fidPlzOrt": 3,
"strasse": "GeroldPenis Str.",
"hausnr": "45",
"rowVersion": 11,
"isDeleted": 0,
"fidPlzOrtNavigation": {
"idPlzOrt": 3,
"plz": "52062",
"ort": "Aachen",
"rowVersion": 10,
"isDeleted": 0
}
},
"idUser": 35,
"fidAnrede": null,
"fidAdresse": 1,
"fidAspnetuser": "a7ab78be-859f-4735-acd1-f06cd832be7e",
"vorname": "Max",
"nachmname": "Leckermann",
"eMail": "kunde#leasing.de",
"rowVersion": 12,
"isDeleted": 0
The RowVersion logic is in the DBContext and only changes the row version when the Entity State is Modified.
foreach (var entity in ChangeTracker.Entries().Where(e => e.State == EntityState.Modified))
{
var saveEntity = entity.Entity as ISavingChanges;
saveEntity.OnSavingChanges();
}
From my investigation over the past 2 days there seem to be two alternatives for EF Core.
1.- As mentioned on this and many similar posts you can use the TrackGraph https://www.mikesdotnetting.com/article/303/entity-framework-core-trackgraph-for-disconnected-data
Problem: This solution marks the entire entity as changed when the ID of the entity is present even though nothing has changed. I get the exact same result as described above, the RowVersion counts up meaning my Entities an all of its Properties have been marked as Modified.
2.- As mentioned in this post
https://blog.tonysneed.com/2017/10/01/trackable-entities-for-ef-core/ you can download a NuGet package and implement the IMergeable and the ITrackable
interfaces on my Entities.
Problem: The Client needs to track the changes in the Model and pass them to the API, which I want to avoid since Angular does not seem to offer a good solution for this.
3.- There is another solution in the book Programming Entity Framework on how EF 6 handles disconnected scenarios, by recording original values Chapter 4: Recording Original Values.
When an Update comes in, the context reads the entity from the DB and then compares the incoming Entity with the DB Entity to see if any changes have been made on the properties and then marks them as Modified.
https://www.oreilly.com/library/view/programming-entity-framework/9781449331825/ch04.html
Problem: Some of the code described in the book is not implementable in EF Core one example is.
((IObjectContextAdapter)this).ObjectContext
.ObjectMaterialized += (sender, args) =>
{
var entity = args.Entity as IObjectWithState;
if (entity != null)
{
entity.State = State.Unchanged;
entity.OriginalValues =
BuildOriginalValues(this.Entry(entity).OriginalValues);
}
};
There must be a way to implement this clever solution of handling changes on the API instead of the Client in Disconnected scenarios, otherwise, this might be a very useful Feature to have in EF Core since most Developers are using it for Disconnected scenarios.
My response is using TrackGraph
Synopsis
I want to Modify only few columns of the entity as well as Add\Modify nested child entities
Here, I am updating Scenario entity and modifying only ScenarioDate.
In its child entity, i.e. navigation property TempScenario, I am adding a new record
In the nested child entity, Scenariostation, I am adding as well modifying the records
public partial class Scenario
{
public Scenario()
{
InverseTempscenario = new HashSet<Scenario>();
Scenariostation = new HashSet<Scenariostation>();
}
public int Scenarioid { get; set; }
public string Scenarioname { get; set; }
public DateTime? Scenariodate { get; set; }
public int Streetlayerid { get; set; }
public string Scenarionotes { get; set; }
public int? Modifiedbyuserid { get; set; }
public DateTime? Modifieddate { get; set; }
public int? Tempscenarioid { get; set; }
public virtual Scenario Tempscenario { get; set; }
public virtual ICollection<Scenario> InverseTempscenario { get; set; }
public virtual ICollection<Scenariostation> Scenariostation { get; set; }
}
public partial class Scenariostation
{
public Scenariostation()
{
Scenariounit = new HashSet<Scenariounit>();
}
public int Scenariostationid { get; set; }
public int Scenarioid { get; set; }
public int Stationid { get; set; }
public bool? Isapplicable { get; set; }
public int? Createdbyuserid { get; set; }
public int? Modifiedbyuserid { get; set; }
public DateTime? Modifieddate { get; set; }
public virtual Scenario Scenario { get; set; }
public virtual Station Station { get; set; }
}
public partial class Station
{
public Station()
{
Scenariostation = new HashSet<Scenariostation>();
}
public int Stationid { get; set; }
public string Stationname { get; set; }
public string Address { get; set; }
public NpgsqlPoint? Stationlocation { get; set; }
public int? Modifiedbyuserid { get; set; }
public DateTime? Modifieddate { get; set; }
public virtual ICollection<Scenariostation> Scenariostation { get; set; }
}
With EF Core, data update in a disconnected scenario is tricky if you don't want to make 2 database round trips.
Even though 2 database trips seems not significant, it can hamper performance if the data table has millions of records.
Also, if there are only few columns to be updated, including columns of nested child entities, Usual Approach will not work
Usual approach
public virtual void Update(T entity)
{
if (entity == null)
throw new ArgumentNullException("entity");
var returnEntity = _dbSet.Attach(entity);
_context.Entry(entity).State = EntityState.Modified;
}
But the problem here for a disconnected EF Core update, if you use this DbContext.Entry(entity).EntityState = EntityState.IsModified, all the columns will updated.
and so some columns will be updated to its default value i.e. null or default data type value.
Further more, some records of ScenarioStation won't be updated at all because the entity state will be UnChanged.
So inorder to update only the columns which are sent from the client, somehow EF Core needs to be told.
Using ChangeTracker.TrackGraph
Recently I found this DbConetxt.ChangeTracker.TrackGraph method which can be used to mark Added, UnChanged state for the entities.
difference is that with TrackGraph, you can add custom logic, as it iteratively navigates through Navigation properties of the entity.
My custom logic using TrackGraph
public virtual void UpdateThroughGraph(T entity, Dictionary<string, List<string>> columnsToBeUpdated)
{
if (entity == null)
throw new ArgumentNullException("entity");
_context.ChangeTracker.TrackGraph(entity, e =>
{
string navigationPropertyName = e.Entry.Entity.GetType().Name;
if (e.Entry.IsKeySet)
{
e.Entry.State = EntityState.Unchanged;
e.Entry.Property("Modifieddate").CurrentValue = DateTime.Now;
if (columnsToBeUpdated.ContainsKey(navigationPropertyName))
{
foreach (var property in e.Entry.Properties)
{
if (columnsToBeUpdated[e.Entry.Entity.GetType().Name].Contains(property.Metadata.Name))
{
property.IsModified = true;
}
}
}
}
else
{
e.Entry.State = EntityState.Added;
}
});
}
With this approach, I am able to easily handle only required column updates as well as new additions/modifications for any of the nested child entities and its columns.
I am having trouble saving children entities via Entity Framework / ASP Identity. It seems to be adding duplicates of everything that is added.
I have tried using a detached graph of the DrivingLicenceModel by TeamMember.DrivingLicence = null in the TeamMemberModel and then working with a detached graph by looking if there is new or old DrivingLicenceCategories but because DrivingLicence links back to TeamMember it causes TeamMember.DrivingLicenceId to be null as it cannot link back to TeamMember.
I have tried Manually adding the EntityState to the DrivingLicence and DrivingLicenceCategories but when I do that it complains that it cannot save two entities with the same primary key.
I assume this is because they way I am copying the entities but I after a lot of looking I am drawing a blank.
If there anyway to copy from TeamMemberRequestModel to TeamMemberModel and then save without the children trying to create clone copies of themselves?
Models
public class TeamMemberModel : IdentityUser
{
public virtual DrivingLicenceModel DrivingLicence { get; set; }
public void ShallowCopy(TeamMemberRequestModel src)
{
this.DateOfBirth = src.DateOfBirth;
if (src.DrivingLicence != null)
{
if (this.DrivingLicence == null)
{
this.DrivingLicence = new DrivingLicenceModel(src.DrivingLicence);
}
else
{
this.DrivingLicence.ShallowCopy(src.DrivingLicence);
}
}
}
public TeamMemberModel() { }
}
public class DrivingLicenceModel
{
[Key]
public int Id { get; set; }
[ForeignKey("TeamMember")]
public string TeamMemberId { get; set; }
[JsonIgnore]
public TeamMemberModel TeamMember { get; set; }
public virtual List<DrivingLicenceCategoryModel> DrivingLicenceCategories { get; set; }
public DrivingLicenceModel() { }
public DrivingLicenceModel(DrivingLicenceModel src)
{
this.ShallowCopy(src);
}
public void ShallowCopy(DrivingLicenceModel src)
{
this.Id = src.Id;
this.IsFullLicence = src.IsFullLicence;
this.IssueDate = src.IssueDate;
this.ExpiryDate = src.ExpiryDate;
this.IssuingAuthority = src.IssuingAuthority;
this.LicenceNumber = src.LicenceNumber;
this.DrivingLicenceCategories = src.DrivingLicenceCategories;
this.DrivingLicencePoints = src.DrivingLicencePoints;
}
}
public class DrivingLicenceCategoryModel
{
[Key]
public int Id { get; set; }
[ForeignKey("DrivingLicence")]
public int DrivingLicenceId { get; set; }
[JsonIgnore]
public DrivingLicenceModel DrivingLicence { get; set; }
}
public class TeamMemberRequestModel
{
public string Id { get; set; }
public virtual DrivingLicenceModel DrivingLicence { get; set; }
}
Context
public class TIERDBContext : IdentityDbContext<TeamMemberModel, RoleModel, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>
{
public TIERDBContext() : base("SARDBConnection") { }
public DbSet<DrivingLicenceModel> DrivingLicences { get; set; }
public DbSet<DrivingLicenceCategoryModel> DrivingLicenceCategories { get; set; }
}
Controller
public async Task<IHttpActionResult> Put(string id, TeamMemberRequestModel teamMember)
{
TeamMemberModel CurrentTeamMember = await this.TIERUserManager.FindByIdAsync(id);
CurrentTeamMember.ShallowCopy(teamMember);
await this.TIERUserManager.UpdateAsync(CurrentTeamMember);
}
you have to create clone property into context class
.
In the context clases you could to use clone method that retiran the entity you send by parameters this duplicarse any entity you pass. Sorry for my english
hope you help
After far to many hours working over this. I have come to an answer. The best way to deal with this is to simply deal with it is to add or attach all entities down the tree.
The controller now attaches all children unless they have an ID of 0, therefore new and uses add instead. Then I use this very useful extension I found here http://yassershaikh.com/c-exceptby-extension-method/ to compare lists to see added and deleted entities in the list. While I don't need the added part as the entity will already be marked to an add state as I use add() it does not harm and I want to use it later with add and delete state changing.
Controller
public async Task<IHttpActionResult> Put(string id, TeamMemberRequestModel teamMember)
{
TIERDBContext IdentityContext = (TIERDBContext)this.TIERUserManager.UserStore().Context;
foreach (DrivingLicenceCategoryModel DrivingLicenceCategory in teamMember.DrivingLicence.DrivingLicenceCategories)
{
if (DrivingLicenceCategory.Id == 0)
{
IdentityContext.DrivingLicenceCategories.Add(DrivingLicenceCategory);
}
else
{
IdentityContext.DrivingLicenceCategories.Attach(DrivingLicenceCategory);
}
}
foreach (DrivingLicencePointModel DrivingLicencePoint in teamMember.DrivingLicence.DrivingLicencePoints)
{
if (DrivingLicencePoint.Id == 0)
{
IdentityContext.DrivingLicencePoints.Add(DrivingLicencePoint);
}
else
{
IdentityContext.DrivingLicencePoints.Attach(DrivingLicencePoint);
}
}
this.DetectAddedOrRemoveAndSetEntityState(CurrentTeamMember.DrivingLicence.DrivingLicenceCategories.AsQueryable(),teamMember.DrivingLicence.DrivingLicenceCategories, IdentityContext);
this.DetectAddedOrRemoveAndSetEntityState(CurrentTeamMember.DrivingLicence.DrivingLicencePoints.AsQueryable(),teamMember.DrivingLicence.DrivingLicencePoints, IdentityContext);
CurrentTeamMember.ShallowCopy(teamMember);
await this.TIERUserManager.UpdateAsync(CurrentTeamMember);
}
I then use a generic that uses ExceptBy to work out what is added and delete from the old team member model to the new team member model.
protected void DetectAddedOrRemoveAndSetEntityState<T>(IQueryable<T> old, List<T> current, TIERDBContext context) where T : class, IHasIntID
{
List<T> OldList = old.ToList();
List<T> Added = current.ExceptBy(OldList, x => x.Id).ToList();
List<T> Deleted = OldList.ExceptBy(current, x => x.Id).ToList();
Added.ForEach(x => context.Entry(x).State = EntityState.Added);
Deleted.ForEach(x => context.Entry(x).State = EntityState.Deleted);
}
It works but it is far from great. It takes two DB queries, getting the original and updating. I just cannot think of any better way to do this.
I'm using System.Linq.Dynamic with EntityFramework. My entities are below:
public class Customer
{
public Customer()
{
CustomerInterests = new List<CustomerInterest>();
}
public int Id { get; set; }
public string Name { get; set; }
public ICollection<CustomerInterest> CustomerInterests { get; set; }
}
public class CustomerInterest
{
public int Id { get; set; }
public int CustomerId { get; set; }
public int CourseId { get; set; }
public Course Course { get; set; }
}
public class Course
{
public int Id { get; set; }
public string Name { get; set; }
}
Below is my method:
public dynamic Get(long customerId)
{
var query = DbContext.Customers.Include("CustomerInterests").Include("CustomerInterests.Course").AsQueryable();
return query.Where(filter => filter.Id == customerId).Select("new(id,name,customerInterests)");
}
JSON result:
{
"id": 2003,
"name": "name customer",
"customerInterests": [
{
"customerId": 2003,
"courseId": 2,
"course": null,
"id": 2016
},
{
"customerId": 2003,
"courseId": 3,
"course": null,
"id": 2017
}
]
}
I'm trying to load the property Course, but it's always returning null as you can see in JSON result.
How can I create the selector new(....) to load correctly the property Course. I've already tried new (customerInterests.course) as customerInterests.course without success.
Do not forget that I am trying to navigate Customer (object) -> CustomerInterests (Collection) -> for each item load Course (object).
I would appreciate if you could help me on this matter.
I had faced that before, and my conclusion is: in Lambda extension methods you can load only 1st level of related object.
You can get list of customerInterests but you can't get foreign records for this list.
But you may use LINQ query instead.
var query=from c in DbContext.Customers
from ci in c.CustomerInterests
from co in ci.Courses
where /// your conditions
select new {
id=c.id,
name=c.name,
customerInterests= new {
customerId= ci.customerId,
courseId=ci.courseId ,
courses= new {
Name=co.Name
/// Other Courses attributes
}
}
}
EDITED
If you're using EF7 , you're able to load second level of foreign records by using ThenInclude method
db.Customers.Include( customer => customer.Orders). ThenInclude( order=> order.OrderDetails);
I'm using EF4.1 for the first time (so be patient with me) but I just cant get to grips with how I can add new items to a sub collection of an object and then save the object.
For example, with the classes below, I can initially save the TravelTicket (containing multiple People) into my database, but as soon as I add a new person and then try to save the TravelTicket again I get:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
Can anyone help?
public class TravelTicket
{
public int Id { get; set; }
public string Destination { get; set; }
public virtual List<Person> Members { get; set; }
}
public class Person
{
public int Id { get; set; }
public string Name{ get; set; }
}
EDITED: All relevant code added as requested:
Domain Models:
public class TravelTicket
{
public int Id { get; set; }
public string Destination { get; set; }
public virtual ICollection<Person> Members { get; set; }
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
The DB Context:
public class TicketContext : DbContext
{
public TicketContext()
: base("TicketStore")
{ }
public DbSet<TravelTicket> TravelTickets { get; set; }
public DbSet<Person> People { get; set; }
}
The Repository (relevant methods only):
public class TicketRepository : ITicketRepository
{
TicketContext context = new TicketContext();
public void InsertOrUpdate(TravelTicket quoteContainer)
{
if (quoteContainer.Id == default(int))
{
// New entity
context.TravelTickets.Add(quoteContainer);
}
else
{
// Existing entity
context.Entry(quoteContainer).State = EntityState.Modified;
}
}
public void Save()
{
try
{
context.SaveChanges();
}
catch (DbEntityValidationException dbEx)
{
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
Trace.TraceInformation("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
}
}
}
}
}
public interface ITicketRepository
{
void InsertOrUpdate(TravelTicket travelTicket);
void Save();
}
The consuming (example) MVC Controller code:
public class TicketSaleController : Controller
{
private readonly ITicketRepository ticketRepository;
public TicketSaleController()
: this(new TicketRepository())
{
}
public TicketSaleController(ITicketRepository ticketRepository)
{
this.ticketRepository = ticketRepository;
}
public ActionResult Index()
{
TravelTicket ticket = new TravelTicket();
ticket.Destination = "USA";
List<Person> travellers = new List<Person>();
travellers.Add(new Person { Name = "Tom" });
travellers.Add(new Person { Name = "Dick" });
travellers.Add(new Person { Name = "Harry" });
ticket.Members = travellers;
ticketRepository.InsertOrUpdate(ticket);
ticketRepository.Save();
Session["Ticket"] = ticket;
return RedirectToAction("Next");
}
public ActionResult Next()
{
TravelTicket ticket = (TravelTicket)Session["Ticket"];
ticket.Members.Add(new Person { Name = "Peter" });
ticket.Members.Add(new Person { Name = "Paul" });
ticketRepository.InsertOrUpdate(ticket);
ticketRepository.Save();
return View();
}
}
The call "ticketRepository.InsertOrUpdate(ticket);" on the "Next" method causes the exception:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
FURTHER EDIT: If I pull the object back from the database after its been saved instead of pulling the object from the session, adding the 2 new persons works OK:
Works:
TravelTicket ticket = ticketRepository.Find(ticketId);
ticket.Members.Add(new Person { Name = "Peter" });
ticket.Members.Add(new Person { Name = "Paul" });
ticketRepository.InsertOrUpdate(ticket);
ticketRepository.Save();
Doesn't Work:
TravelTicket ticket = (TravelTicket)Session["Ticket"];
ticket.Members.Add(new Person { Name = "Peter" });
ticket.Members.Add(new Person { Name = "Paul" });
ticketRepository.InsertOrUpdate(ticket);
ticketRepository.Save();
I'd need to see the code you are using to add items and then persist them. Until that a few generic advice.
It seems like you're using a long-living context to do your stuff. It's a good practice to use short living context, like this:
Instance context
Do a single operation
Dispose the context
Rinse and repeat for every operation you have to do. While following this good practice, you could be indirectly solving your problem.
Again, for more specific help, please post the code you're using ;)
In your mapping class for person, you may need do something like this
Property(p => p.Id)
.StoreGeneratedPattern = StoreGeneratedPattern.Identity;