I have a Topic class:
public class Topic : BaseEntity
{
[MaxLength(50)]
public string Name { get; set; }
public bool Active { get; set; }
public ICollection<Content> Contents { get; set; }
}
And a Content class:
public class Content : BaseEntity
{
[Required]
[RegularExpression(@"^[\d \w \s]+$", ErrorMessage = "The field {0} Characters are not allowed.")]
public string Name { get; set; }
[Required]
[Url]
public string URL { get; set; }
[Required]
[Range(0.1, double.MaxValue, ErrorMessage = "The field {0} must be greater than {1}.")]
public double StartingVersion { get; set; }
[Required]
[Range(0.1, double.MaxValue, ErrorMessage = "The field {0} must be greater than {1}.")]
public double EndingVersion { get; set; }
[Required]
[MinLength(1)]
public string Summary { get; set; }
[Required]
public bool Active { get; set; }
[Required]
public ICollection<Topic> Topics { get; set; }
}
The BaseEntity looks like this:
public class BaseEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int CreatedBy { get; set; }
public DateTime CreatedDate { get; set; }
public int ModifiedBy { get; set; }
public DateTime ModifiedDate { get; set; }
}
My DataContext looks like this:
public DataContext(DbContextOptions<DataContext> options) : base(options) { }
private DbSet<Topic> Topics { get; set; }
private DbSet<Content> Contents { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Content>()
.HasIndex(u => u.Id)
.IsUnique();
builder.Entity<Content>().HasMany<Topic>(t => t.Topics).WithMany(c => c.Contents);
base.OnModelCreating(builder);
}
And I'm trying to use a generic Repository. The saveEntity looks like this:
public async Task<T> SetEntity<T>(T entity) where T : BaseEntity
{
using (var scope = _serviceProvider.CreateScope())
{
entity.CreatedDate = DateTime.Now;
var _dbContext = scope.ServiceProvider.GetRequiredService<DataContext>();
_dbContext.Add(entity);
await _dbContext.SaveChangesAsync("me@me.com");
return entity;
}
}
And the Content Service method that does the creation of Contents looks like this:
public async Task<ContentDTO> AddContentAsync(ContentDTO content)
{
Expression<Func<Content, bool>> exp = i => i.URL == content.URL && i.Active == true;
if (_dataRepository.GetEntities(exp).Any())
{
throw new DuplicateWaitObjectException("Object already exist");
}
CheckObjectives(content.Objectives);
foreach (var item in content.Topics)
{
Expression<Func<Topic, bool>> expTopic = i => i.Id == item.Id && i.Active == true;
var topic = await _dataRepository.GetEntity(expTopic);
if(topic == null)
{
throw new KeyNotFoundException($"Topic with ID {item.Id} not found");
}
}
Content toSaveContent = Mapping.Mapper.Map<Content>(content);
_modelHelper.ModelValidation(toSaveContent);
toSaveContent.Active = true;
Content newContent = await _dataRepository.SetEntity(toSaveContent);
return Mapping.Mapper.Map<ContentDTO>(newContent);
}
My problem is that when I try to create a new Content EF fails to detect that the Topics included in the body of the Content are existing ones and tries to add them as new in the DB. Obviously, this raises a SQL exception saying I can't define the Id of the Topic.
What I'm missing??
Thank you for your help
EDIT: Also tried to retrieve the Topics from context, but didn't work either:
public async Task<ContentDTO> AddContentAsync(ContentDTO content)
{
Expression<Func<Content, bool>> exp = i => i.URL == content.URL && i.Active == true;
if (_dataRepository.GetEntities(exp).Any())
{
throw new DuplicateWaitObjectException("Object already exist");
}
CheckObjectives(content.Objectives);
Content toSaveContent = Mapping.Mapper.Map<Content>(content);
_modelHelper.ModelValidation(toSaveContent);
toSaveContent.Active = true;
toSaveContent.Topics = new List<Topic>();
foreach (var item in content.Topics)
{
Expression<Func<Topic, bool>> expTopic = i => i.Id == item.Id && i.Active == true;
var topic = await _dataRepository.GetEntity(expTopic);
if(topic == null)
{
throw new KeyNotFoundException($"Topic with ID {item.Id} not found");
}
toSaveContent.Topics.Add(topic);
}
Content newContent = await _dataRepository.SetEntity(toSaveContent);
return Mapping.Mapper.Map<ContentDTO>(newContent);
}
EDIT2: You are right, Guru Stron, I'll extract the GetEntity from the foreach and just take them all before. This is my GetEntity method in the generic repository:
public async Task<T> GetEntity<T>(Expression<Func<T, bool>> predicate) where T : BaseEntity
{
using (var scope = _serviceProvider.CreateScope())
{
var _dbContext = scope.ServiceProvider.GetRequiredService<DataContext>();
return _dbContext.Set<T>().Where(predicate).FirstOrDefault();
}
}