[Asp Core, Ef Core] Duplikacja encji w pamięci podczas zapisu do bazy.

0

Witam, mam pewien problem. Przykładowo mamy nowego użytkownika, który nie posiada żadnej postaci, następnie chce ją utworzyć, wywołuje metodę CreateCharacter. Jeśli pomyślnie utworzę postać i dodam ją do listy postaci użytkownika wszystko jest ok, user.Characters.Count == 1 ale po wywołaniu await _context.SaveChangesAsync(); postać się duplikuje user.Characters.Count == 2, prawdopodobnie tylko w pamięci bo w bazie danych jest tylko jedna. Czy ktoś wie w czym tkwi problem?

public abstract class Entity
{
	public long Id { get; }

	protected Entity()
	{
	}

	protected Entity(long id)
	{
		Id = id;
	}

	public override int GetHashCode()
	{
		return (GetType().FullName + Id).GetHashCode();
	}

	public static bool operator ==(Entity? left, Entity? right)
	{
		if (left is null ^ right is null)
			return false;

		return left is null || left.Equals(right);
	}

	public static bool operator !=(Entity? left, Entity? right)
	{
		return !(left == right);
	}
}

public class Character : Entity
{
	public string Name { get; set; }
	// ...
}

public class User : Entity
{
	public string Username { get; set; }
	public ICollection<Character> Characters { get; set; } = new HashSet<Character>();
	// ...

	public User()
	{
	}

	public User(long id) 
		: base(id)
	{
	}
}

public class CreateCharacterHandler
{
	private readonly DataContext _context;
	private readonly ILoggedUser _loggedUser;
	private readonly IMapper _mapper;

	public CreateCharacterHandler(DataContext context, ILoggedUser loggedUser, IMapper mapper)
	{
		_context = context;
		_loggedUser = loggedUser;
		_mapper = mapper;
	}

	public async Task<Result<IReadOnlyList<CharacterDto>>> CreateCharacter(CreateCharacterCommand command)
	{
		var user = await _context.Users
			.Include(u => u.Characters)
			.FirstOrDefaultAsync(u => u.Id == _loggedUser.Id);

		if (user == null)
			return Result<IReadOnlyList<CharacterDto>>.Fail(ErrorCode.UserNotFound);

		var character = _mapper.Map<Character>(command);

		var canAdd = user.CanAddCharacter(character);
		if (!canAdd.IsSuccess)
			return Result<IReadOnlyList<CharacterDto>>.Fail(canAdd.Message);

		user.Characters.Add(character);
		// user.Characters.Count; == 1

		await _context.SaveChangesAsync();
		// user.Characters.Count; == 2

		return Result<IReadOnlyList<CharacterDto>>.Success(
			_mapper.Map<IReadOnlyList<CharacterDto>>(user.Characters));
	}
}
0

Jesteś pewien, że characters.count == 1 nie jest już przed wywołaniem linijki 83 ?

Jeśli tak, to może metoda CreateCharacter wywołana jest dwukrotnie i zanim zapiszesz zmiany w bazie i przejdziesz do kolejnej linijki debugując, kolekcja została już edytowana z "drugiego wywołania" ?
Spróbuj zalockować ICollection<Character> Characters

0

Debuger w linijce 82 pokazuje user.Characters.Count == 0 potem dodaję postać i w linijce 84 user.Characters.Count == 1. W nowm projekcie "błąd" ciągle się powtarza.

POST

[
    {
        "name": "Test",
        "id": 1
    },
    {
        "name": "Test",
        "id": 1
    }
]

GET

[
    {
        "name": "Test",
        "id": 1
    }
]

POST

[
    {
        "name": "Test",
        "id": 1
    },
    {
        "name": "Test",
        "id": 2
    },
    {
        "name": "Test",
        "id": 2
    }
]
public class Startup
{
	// ... wszystko jest domyślne dodany tylko DbContext
	public void ConfigureServices(IServiceCollection services)
	{
		services.AddControllers();
		services.AddDbContext<DataContext>();
	}
}

public class DataContext : DbContext
{
	public DbSet<User> Users { get; set; }

	public DataContext()
	{
		Database.EnsureCreated();
	}

	protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
	{
		optionsBuilder.UseInMemoryDatabase("test");
	}

	protected override void OnModelCreating(ModelBuilder modelBuilder)
	{
		modelBuilder.Entity<User>().HasKey(x => x.Id);
		modelBuilder.Entity<User>().HasMany(x => x.Characters);
		modelBuilder.Entity<User>().HasData(new User(1) { Username = "Test" });

		modelBuilder.Entity<Character>().HasKey(x => x.Id);
	}
}

public class Character : Entity // klasa Entity jak w pierwszym poście, żadnej zmiany
{
	public string Name { get; set; }
}

public class User : Entity
{
	public string Username { get; set; }

	public ICollection<Character> Characters { get; set; } = new HashSet<Character>();

	public User(long id) : base(id) {}
}

[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{
	private readonly DataContext _context;

	public TestController(DataContext context)
	{
		_context = context;
	}

	[HttpGet]
	public IActionResult Get()
	{
		var user = _context.Users
			.Include(u => u.Characters)
			.FirstOrDefault(u => u.Id == 1);

		return Ok(user.Characters);
	}

	[HttpPost]
	public IActionResult Create()
	{
		var user = _context.Users
			.Include(u => u.Characters)
			.FirstOrDefault(u => u.Id == 1);

		// user.Characters.Count == 0

		var character = new Character { Name = "Test" };

		user.Characters.Add(character);

		// user.Characters.Count == 1

		_context.SaveChanges();

		// user.Characters.Count == 2

		return Ok(user.Characters);
	}
}

1 użytkowników online, w tym zalogowanych: 0, gości: 1