2

I'm working on my first webapi in ASP.NET Core. I have two entities:

public class Event : Entity
{
    private ISet<Ticket> _tickets = new HashSet<Ticket>();
    public string Name { get; protected set; }
    public string Description { get; protected set; }
    public DateTime StartDate { get; protected set; }
    public DateTime EndDate { get; protected set; }
    public DateTime CreatedAt { get; protected set; }
    public DateTime UpdatedAt { get; protected set; }
    public IEnumerable<Ticket> Tickets => _tickets;

    //.....

     public void AddTickets(int amount, decimal price)
    {
        var seating = _tickets.Count + 1;
        for (var i = 0; i < amount; i++)
        {
            _tickets.Add(new Ticket(this, seating, price));
            seating++;
        }
    }
 }

  public class Ticket : Entity
{
    public Guid EventId { get; protected set; }
    public decimal Price { get; protected set; }
    //... and other properties
}

When I'm trying to create new event and assign tickets to it:

public class EventController : Controller
{
   [HttpPost]
    public async Task<IActionResult> AddEvent([FromBody] CreateEventCommand createEventCommand)
    {
        createEventCommand.Id = Guid.NewGuid();
        await _eventService.AddAsync(createEventCommand.Id,
            createEventCommand.Name,
            createEventCommand.Description,
            createEventCommand.StartDate,
            createEventCommand.EndDate);

        await _eventService.AddTicketAsync(createEventCommand.Tickets,
            createEventCommand.Price,
            createEventCommand.Id);

        return Created($"/events/{createEventCommand.Name}", null);
        
    }
 }
 
 public class EventService : IEventService
 {
            public async Task AddAsync(Guid id, string name, string description, DateTime startDate, DateTime endDate)
    {
        var @event = await _eventRepository.GetAsync(id);
        if(@event != null)
        {
            throw new Exception($"Event with this id: {@event.Id} already exist");
        }

        var eventToAdd = new Event(id, name, description, startDate, endDate);
        await _eventRepository.AddAsync(eventToAdd);
    }

    public async Task AddTicketAsync(int amount, decimal price, Guid eventId)
    {
        var @event = await _eventRepository.GetAsync(eventId);
        if (@event == null)
        {
            throw new Exception($"Event with this id: {@event.Id} already exist");
        }

        @event.AddTickets(amount, price);
        await _eventRepository.UpdateAsync(@event);

    }
  }

 public class EventRepository : IEventRepository
 {
    public async Task AddAsync(Event @event)
    {
        _context.Events.Add(@event);
        await _context.SaveChangesAsync();
    }
    
     public async Task UpdateAsync(Event @event)
    {
        _context.Events.Update(@event);
        await _context.SaveChangesAsync();
    }
 }  

The query which is executed on Tickets table is

  UPDATE [Tickets] SET [EventId] = @p700, [Price] = @p701, [PurchasedAt] = @p702, [Seating] = @p703, [UserId] = @p704, [Username] = @p705
  WHERE [Id] = @p706;
  SELECT @@ROWCOUNT;

After that It throws an exception:

Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded.

My question is: shouldn't it be INSERT queries instead of UPDATE in this case and what could be a reason of concurrency problem in this case? I'm new to .NET and concurency in general, would be grateful for any help.

5
  • 2
    You could configure the Ticket.Id property to ValueGeneratedNever [DatabaseGenerated(DatabaseGeneratedOption.None)]. Commented Sep 24, 2020 at 7:35
  • 1
    You are not adding the Ticket via context.Tickets.Add(), but instead adding them to the event.Tickets collection. So they are not set to the added entity state. Instead they are logically untracked. Change tracker treats untracked entities as modified in this case. Commented Sep 24, 2020 at 7:38
  • Thanks for your comment. It looks like you're right. If I Add an Event with Tickets in a single step (context.Events.AddAsync(//Event with tickets) and then SaveAsync() it's working fine. If it's happening in a sequence: 'Add an Event > Save > Upate Event entity with tickets > Save' then I get an error. How can I change the behavior of Change tracker then? By adding: .UseQueryTrackingBehavior(QueryTrackingBehavior.TrackAll)) to options in AddDbContext method it doesn't work. Commented Sep 24, 2020 at 13:56
  • I think i found the reason basing on your first comment. All of my entities inherited from Entity base class with constructor: protected Entity() { Id = Guid.NewGuid(); } When I removed the Id generation, it's working fine... Commented Sep 24, 2020 at 14:07
  • I'm not sure if it make sense, but It looks like Tickets entities had already Id != null when they were generated so EF wanted to update them instead of insert new records... Commented Sep 24, 2020 at 14:16

1 Answer 1

0

In your AddTicketAsync method you retrieve an attached entity of your Event object.

You should be able to update it by calling your AddTickets method following by a save changes.

 @event.AddTickets(amount, price);
 _eventRepository.your_context.SaveChangesAsync();

In this way you will save the state of your updated Event Entity

You got a concurrency error because your are trying to update an attached entity. The following post could help you How to update record using entity framework core?

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for your reply. Unofortunately It didn't work. Still returns the same error...
You can try to detach your eventToAdd var entity = _context.Events.Add(@event); _context.detach(entity); await _context.SaveChangesAsync();

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.