as you know, jsoncolumn support has arrived for efcore7.
I quickly used
yes, I had no problems with creating columns with migration. I added new data
but i have the following problem in query operation
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<IdentitySchema>().OwnsMany(
identitySchema => identitySchema.AuthorityCodes, ownedNavigationBuilder =>
{
var q=ownedNavigationBuilder.ToJson();
})
.OwnsMany(
identitySchema => identitySchema.UserIds, ownedNavigationBuilder =>
{
var x= ownedNavigationBuilder.ToJson();
}); ;
base.OnModelCreating(builder);
}
public class UserAg
{
public string UserId { get; set; }
}
_context.IdentitySchema.Select(f => new
{
f.UserIds,
f.AuthorityCodes
}).Where(f => f.UserIds.Any(f => f.UserId == "1")).ToList();
Related
TLDR: adding a new untracked entity type to a existing DTO's nested collection => existing DTO is mapped to its existing tracked entity type => EF Core tracker now sees the new entity's state as Modified instead of Added => error, there is nothing to be updated.
I ran into issues with AutoMapper and Entity Framework Core after we updated these packages to:
.NET Core 3.1
AutoMapper 9.0.0
EF Core 3.1.3
There is a simple scenario:
We have a DTO D that represents an entity E. Both the DTO and entity have a collection of child objects of type R that are EF entities as well (not the best design, but keep it like that, please).
I create a new DTO D (with empty collection), map it to entity E and save to the database. OK
I load the E from database, map it to DTO D (E is now tracked in EF Core). OK
I create a new object R and add it to the collection of DTO D. OK
I map the DTO D to its existing entity E, that is now tracked by the EF Core
Now there is an item for the object R in EF tracker that says, that R is Modified but definitely shoud be Added.
There is a minimumal working example of this behavior:
using AutoMapper;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using System;
using System.Collections.Generic;
using System.Linq;
namespace AutomapperEFTest
{
class TestContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
string databaseName = "TestDB";
optionsBuilder
.UseInMemoryDatabase(databaseName: databaseName)
.ConfigureWarnings(x => x.Ignore(InMemoryEventId.TransactionIgnoredWarning));
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<TestEntity>(e =>
{
e.ToTable("TestEntity");
e.HasKey(t => t.Id);
e.Property(t => t.SomeProperty);
e.HasMany(t => t.RefValues)
.WithOne()
.HasForeignKey(r => r.EntityId);
});
modelBuilder.Entity<RefValue>(e =>
{
e.ToTable("RefValue");
e.HasKey(r => r.Id);
e.Property(r => r.EntityId)
.HasColumnName("EntityId");
e.Property(r => r.Value)
.HasColumnName("Value");
});
}
public DbSet<TestEntity> Entities => Set<TestEntity>();
public DbSet<RefValue> RefValues => Set<RefValue>();
}
class TestEntity
{
public Guid Id { get; set; }
public string SomeProperty { get; set; }
public ICollection<RefValue> RefValues { get; set; }
}
class TestDto
{
public TestDto()
{
RefValues = new List<RefValue>();
}
public Guid Id { get; set; }
public string SomeProperty { get; set; }
public List<RefValue> RefValues { get; set; }
}
class RefValue
{
public Guid Id { get; set; }
public Guid EntityId { get; set; }
public string Value { get; set; }
}
class Program
{
static void Main(string[] args)
{
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<TestDto, TestEntity>()
.ForMember(d => d.Id, o => o.MapFrom(s => s.Id))
.ForMember(d => d.SomeProperty, o => o.MapFrom(s => s.SomeProperty))
.ForMember(d => d.RefValues, o => o.MapFrom(s => s.RefValues))
.ReverseMap();
});
using (var context = new TestContext())
{
var mapper = new Mapper(config);
#region Works
/*
* Adding both parent and child in one step works.
var newDto = new TestDto() { Id = Guid.Parse("4C1596D7-C080-4E56-B2F6-1BAEBDC1CF7D"), SomeProperty = "Whatever" };
var newRef = new RefValue() { Id = Guid.Parse("526DC1A6-130B-41DC-9F74-32F9C807D7F5"), EntityId = Guid.Parse("4C1596D7-C080-4E56-B2F6-1BAEBDC1CF7D"), Value = "New Ref Value" };
newDto.RefValues.Add(newRef);
var entityToSave = mapper.Map<TestEntity>(newDto);
context.Add(entityToSave);
context.SaveChanges();
*/
#endregion
#region Data preparation
var newDto = new TestDto(){Id = Guid.Parse("4C1596D7-C080-4E56-B2F6-1BAEBDC1CF7D"),SomeProperty = "Whatever"};
var entity = mapper.Map<TestEntity>(newDto);
context.Entities.Add(entity);
context.SaveChanges();
#endregion
//Simple scenario - add a new item to a collection of an existing item
var loadedEntity = context.Entities.FirstOrDefault();
var loadedDto = mapper.Map<TestDto>(loadedEntity);
var newRef = new RefValue(){Id = Guid.Parse("526DC1A6-130B-41DC-9F74-32F9C807D7F5"),EntityId = Guid.Parse("4C1596D7-C080-4E56-B2F6-1BAEBDC1CF7D"),Value = "New Ref Value"};
loadedDto.RefValues.Add(newRef);
//ensure it will be mapped to existing tracked item
var entityToSave = mapper.Map(loadedDto, loadedEntity, typeof(TestDto), typeof(TestEntity));
//Now the inner state of the newRef in the tracker is Modified but definitely should be Added
//check it out: context.ChangeTracker.Entries<RefValue>().ToArray();
//throws exception
context.SaveChanges();
}
}
}
}
How should be the AutoMapper configured to prevent this behavior?
This worked for us in previous versions - .NET Core 2.1, EF Core 1.x, AutoMapper 8.
I have a project with 4 classes:Direction, Area, Section and Local. Direction have many areas, Area have many sections and section have many locals. Local have positives locals and negatives locals, therefore Local entity will have a self many to many relationship. I'm using Automapper for convert LocalDto to Local, but when i try to update this entity with positives locals and/or negatives locals inserted, the system generate this exception:
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
So, they are my mapper classes for my entities:
*******DirectionMapper*******
public static class DirectionMappers
{
public static void SettingMappingDirectionToDirectionDto()
{
Mapper.CreateMap<Direction, DirectionDto>()
.ForMember(directionDto => directionDto.AreasDtosList,
mc => mc.MapFrom(direction => direction.AreasCollection));
}
public static void SettingMappingDirectionDtoToDirection()
{
Mapper.CreateMap<DirectionDto, Direction>()
.ForMember(direction => direction.AreasCollection,
mc => mc.MapFrom(directionDto => directionDto.AreasDtosList));
}
public static void SettingMappingDirectionToString()
{
Mapper.CreateMap<Direction, string>().ConvertUsing(direction => direction.Name ?? string.Empty);
}
}
********AreaMapper**********
public class AreaMappers
{
public static void SettingMappingAreaToAreaDto()
{
Mapper.CreateMap<Area, AreaDto>()
.ForMember(areaDto => areaDto.SectionsDtosList, mc => mc.MapFrom(area => area.SectionsCollection))
.ForMember(areaDto => areaDto.DirectionDto, mc => mc.MapFrom(area => area.Direction));
}
public static void SettingMappingAreaDtoToArea()
{
Mapper.CreateMap<AreaDto, Area>()
.ForMember(area => area.SectionsCollection, mc => mc.MapFrom(areaDto => areaDto.SectionsDtosList))
.ForMember(area => area.Direction,mc=> mc.MapFrom(areaDto=> areaDto.DirectionDto));
}
public static void SettingMappingAreaToString()
{
Mapper.CreateMap<Area, string>().ConvertUsing(area => area.Name ?? string.Empty);
}
}
******SectionMapper*******************
public class SectionMappers
{
public static void SettingMappingSectionToSectionDto()
{
Mapper.CreateMap<Section, SectionDto>()
.ForMember(sectionDto => sectionDto.LocalsDtosList, mc => mc.MapFrom(section => section.LocalsCollection))
.ForMember(sectionDto => sectionDto.AreaDto, mc => mc.MapFrom(section => section.Area));
}
public static void SettingMappingSectionDtoToSection()
{
Mapper.CreateMap<SectionDto, Section>()
.ForMember(section => section.LocalsCollection,
mc => mc.MapFrom(sectionDto => sectionDto.LocalsDtosList))
.ForMember(section => section.Area, mc => mc.MapFrom(sectionDto => sectionDto.AreaDto));
}
public static void SettingMappingSectionToString()
{
Mapper.CreateMap<Section, string>().ConvertUsing(section => section.Name ?? string.Empty);
}
}
******LocalMapper (the main course)******
public static class LocalMappers
{
public static void SettingMappingLocalToLocalDto()
{
Mapper.CreateMap<Local, LocalDto>()
.ForMember(localDto => localDto.PositivesLocalsDtos,
mc => mc.MapFrom(local => local.PositivesLocals)
)
.ForMember(localDto => localDto.NegativesLocalsDtos,
mc => mc.MapFrom(local => local.NegativesLocals)
)
.ForMember(localDto => localDto.SectionDto, mc => mc.MapFrom(local => local.Section));
}
public static void SettingMappingLocalDtoToLocal()
{
Mapper.CreateMap<LocalDto, Local>()
.ForMember(local => local.PositivesLocals,
mc => mc.MapFrom(localDto => localDto.PositivesLocalsDtos)
)
.ForMember(local => local.NegativesLocals,
mc => mc.MapFrom(localDto => localDto.NegativesLocalsDtos)
)
.ForMember(local => local.Section, mc => mc.MapFrom(localDto => localDto.SectionDto));
}
public static void SettingMappingLocalToString()
{
Mapper.CreateMap<Local, string>().ConvertUsing(local => local.Number ?? string.Empty);
}
}
Well, this's a service method for Local update:
public AppOperationResult Update(int id, LocalDto localDto)
{
var appOperationResult = CommunValidations.IsDtoNull(localDto);
if (appOperationResult != null) return appOperationResult;
var tupleValidation = localDto.IsModelDtoValidateForUpdate(id);
var isValidate = tupleValidation.Item1;
if (isValidate)
{
if (TryUpdateLocalFromLocalDto(id, localDto)) return AppOperationResult.Successful();
}
string messageError = tupleValidation.Item2;
return AppOperationResult.WithError(messageError);
}
And these are the methods I did to add positive and negative locals (i call them AdjacentLocals):
public AppOperationResult AddAdjacentLocalsToLocal(AdjacentLocalsToLocalDto adjacentLocalsToLocal)
{
var localDto = adjacentLocalsToLocal.LocalToModify;
var appOperationResult = CommunValidations.IsDtoNull(localDto);
if (appOperationResult != null) return appOperationResult;
var tupleValidation = localDto.IsModelDtoValidate();
var isValidate = tupleValidation.Item1;
if (isValidate)
{
if (TryToAddAdjacentLocalsToLocal(adjacentLocalsToLocal, localDto))
return AppOperationResult.Successful();
}
string messageError = tupleValidation.Item2;
return AppOperationResult.WithError(messageError);
}
private bool TryToAddAdjacentLocalsToLocal(AdjacentLocalsToLocalDto adjacentLocalsToLocal, LocalDto localDto)
{
var positiveLocals = adjacentLocalsToLocal.PositiveLocals;
var negativeLocals = adjacentLocalsToLocal.NegativeLocals;
var positiveslocalsRepeated = positiveLocals.Intersect(localDto.PositivesLocalsDtos);
positiveLocals.RemoveAll(x => positiveslocalsRepeated.Contains(x));
var negativeslocalsRepeated = negativeLocals.Intersect(localDto.NegativesLocalsDtos);
negativeLocals.RemoveAll(x => negativeslocalsRepeated.Contains(x));
localDto.PositivesLocalsDtos = new List<LocalDto>(positiveLocals);
localDto.NegativesLocalsDtos = new List<LocalDto>(negativeLocals);
return TryUpdateLocalFromLocalDto(localDto.Id, localDto);
}
private bool TryUpdateLocalFromLocalDto(int idLocal, LocalDto localDto)
{
var local = _localServices.GetById(idLocal);
local.PositivesLocals.Clear();
local.NegativesLocals.Clear();
_localServices.Update(local);
if (local != null)
{
localDto.Id = idLocal;
var localUpdated = _mappingServices.Map(localDto, local);
_localServices.Update(localUpdated);
return true;
}
return false;
}
********LocalDto*************
public class LocalDto
{
public int Id { get; set; }
public string Number { get; set; }
public float Volumen { get; set; }
public int NumberMaxPeople { get; set; }
public SectionDto SectionDto { get; set; }
public List<LocalDto> PositivesLocalsDtos { get; set; }
public List<LocalDto> NegativesLocalsDtos { get; set; }
}
I'm working using ASP.NET WEB API philosophy,that's why I pass the list of adjacent places with a JSON (correctly), because I think the relationship between the objects in the lists with the database record is lost, but I do not understand why, since these local DTOs they are mapped correctly and return the corresponding local object. However, when I update a local with out a any list of positives or negatives locals, no problem.. so i think that problem is with the self many to many relationship.
I have traced the code several times, I check if all the entities have their relationships and everything seems to be fine, but when I try to update the Local entity inserting adjacents locals(positive and negative local) gives me the error that I mentioned above. So, i . I await your answers.Regards
I think what is happening is the following some entity entity framework that is not linking the existing sections in your database when you use the service of automapper, so I suggest that in your Dto not use the relationships for the other dto, for example:
public class LocalDto
{
public int Id { get; set; }
public string Number { get; set; }
public float Volumen { get; set; }
public int NumberMaxPeople { get; set; }
public SectionDto SectionDto { get; set; }
public List<LocalDto> PositivesLocalsDtos { get; set; }
public List<LocalDto> NegativesLocalsDtos { get; set; }
}
change it to :
public class LocalDto
{
public int Id { get; set; }
public string Number { get; set; }
public float Volumen { get; set; }
public int NumberMaxPeople { get; set; }
public int SectionId { get; set; }
public List<LocalDto> PositivesLocalsDtos { get; set; }
public List<LocalDto> NegativesLocalsDtos { get; set; }
}
and you must also change the mapper associated with these entities,this must be removed from the class LocalMappers,
.ForMember(localDto => localDto.SectionDto, mc => mc.MapFrom(local => local.Section));
This solution is for all your Dtos that have relations, link them to the id of the entity with which it is related not with the Dtos
I hope I've helped
I am trying to create a many-to-many relation between my User and Request classes. The migration should generate an intermediate table but its generating a one-to-many in both table.
public class ApplicationUser : IdentityUser
{
...
public virtual ICollection<Request> Requests{ get; set; }
}
public partial class Request
{
...
public virtual ICollection<ApplicationUser> Users{ get; set; }
}
public partial class _12 : DbMigration
{
public override void Up()
{
AddColumn("dbo.AspNetUsers", "Request_Id", c => c.Int());
AddColumn("dbo.Requests", "ApplicationUser_Id", c => c.String(maxLength: 128));
CreateIndex("dbo.AspNetUsers", "Request_Id");
CreateIndex("dbo.Requests", "ApplicationUser_Id");
AddForeignKey("dbo.AspNetUsers", "Request_Id", "dbo.Requests", "Id");
AddForeignKey("dbo.Requests", "ApplicationUser_Id", "dbo.AspNetUsers", "Id");
}
...
}
Got it working with he fluent API.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ApplicationUser>()
.HasMany(t => t.Requests)
.WithMany(t => t.Users);
}
Giving me this
public partial class _12 : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.ApplicationUserRequests",
c => new
{
ApplicationUser_Id = c.String(nullable: false, maxLength: 128),
Request_Id = c.Int(nullable: false),
})
.PrimaryKey(t => new { t.ApplicationUser_Id, t.Request_Id })
.ForeignKey("dbo.AspNetUsers", t => t.ApplicationUser_Id, cascadeDelete: true)
.ForeignKey("dbo.Requests", t => t.Request_Id, cascadeDelete: true)
.Index(t => t.ApplicationUser_Id)
.Index(t => t.Request_Id);
}
...
}
I don't have to use the fluent api for table I added myself but the user table is different.
I have these three lines of c# code using Moq, how can I write a single line?
JobQueueRepository.Setup(r => r.UpdateJobQueueStatus(DefaultJobId, JobStatus.Success)).Callback(() => statuses.Add(JobStatus.Success));
JobQueueRepository.Setup(r => r.UpdateJobQueueStatus(DefaultJobId, JobStatus.Failed)).Callback(() => statuses.Add(JobStatus.Failed));
JobQueueRepository.Setup(r => r.UpdateJobQueueStatus(DefaultJobId, JobStatus.Running)).Callback(() => statuses.Add(JobStatus.Running));
Thanks for the help.
There is a piece of code you are asking for
JobQueueRepository
.Setup(it => it.UpdateJobQueueStatus(DefaultJobId, It.IsAny<JobStatus>()))
.Callback<int, JobStatus>((id, status) => statuses.Add(status));
And a test that tests how it works
[TestClass]
public class TestClass
{
[TestMethod]
public void TestMethod()
{
var statuses = new List<JobStatus>();
var JobQueueRepository = new Mock<IJobQueueRepository>();
int DefaultJobId = 100500;
JobQueueRepository
.Setup(it => it.UpdateJobQueueStatus(DefaultJobId, It.IsAny<JobStatus>()))
.Callback<int, JobStatus>((id, status) => statuses.Add(status));
JobQueueRepository.Object.UpdateJobQueueStatus(DefaultJobId, JobStatus.Failed);
JobQueueRepository.Object.UpdateJobQueueStatus(DefaultJobId, JobStatus.Running);
JobQueueRepository.Object.UpdateJobQueueStatus(DefaultJobId, JobStatus.Success);
statuses.Should().HaveCount(3);
statuses.Should().Contain(JobStatus.Failed);
statuses.Should().Contain(JobStatus.Running);
statuses.Should().Contain(JobStatus.Success);
}
public enum JobStatus
{
Success,
Failed,
Running
}
public interface IJobQueueRepository
{
void UpdateJobQueueStatus(int id, JobStatus status);
}
}
You can easily create an extension method to do that as below.
public class Class1
{
[Test]
public void CallBackDemo() {
var statuses = new List<JobStatus>();
var jobQueueRepositoryStub = new Mock<IJobQueueRepository>();
const int defaultJobId = 100500;
jobQueueRepositoryStub.Setup(r => r.UpdateJobQueueStatus(defaultJobId, JobStatus.Success))
.Callback( new Action[]
{
() => statuses.Add(JobStatus.Success),
() => statuses.Add(JobStatus.Failed),
() => statuses.Add(JobStatus.Running)
});
var sut = new Sut(jobQueueRepositoryStub.Object);
sut.Do(defaultJobId);
Assert.True(statuses.Count == 3);
Assert.True(statuses.Any(x => x == JobStatus.Failed));
Assert.True(statuses.Any(x => x == JobStatus.Running));
Assert.True(statuses.Any(x => x == JobStatus.Success));
}
Callback extension method:
public static class Ext
{
public static void Callback<TRepo>(this ISetup<TRepo> repo, IEnumerable<Action> actions ) where TRepo : class {
foreach (var action in actions) {
action();
}
}
}
Sut (System Under Test) and other classes:
public enum JobStatus { Success, Failed, Running }
public interface IJobQueueRepository {
void UpdateJobQueueStatus(int id, JobStatus status);
}
public class Sut {
private readonly IJobQueueRepository _repository;
public Sut(IJobQueueRepository repository) {
_repository = repository;
}
public void Do(int jobId) {
_repository.UpdateJobQueueStatus(jobId, JobStatus.Success);
_repository.UpdateJobQueueStatus(jobId, JobStatus.Failed);
_repository.UpdateJobQueueStatus(jobId, JobStatus.Running);
}
}
I am trying to model the following using EF 4.1 and cannot get past this exception ("Collection was modified; enumeration operation may not execute").
Models:
public class Workflow
{
public List<WorkflowStage> Stages { get; set; }
}
public class WorkflowStage
{
public virtual List<WorkflowNextStage> WorkflowNextStages { get; set; }
}
public abstract class WorkflowNextStage
{
public virtual WorkflowStage NextStage { get; set; }
}
public class SuccessStage : WorkflowNextStage
{
}
public class FailureStage : WorkflowNextStage
{
}
Configuration:
modelBuilder.Entity<WorkflowStage>()
.HasMany(x => x.WorkflowNextStages)
.WithRequired()
.Map(m => m.MapKey("CurrentStageId"));
modelBuilder.Entity<WorkflowNextStage>()
.HasRequired(x => x.NextStage)
.WithMany()
.Map(x => x.MapKey("NextStageId"))
.WillCascadeOnDelete(false);
Failing code:
using (var ctx = new SBContext())
{
var workflow = new Workflow();
var stage = new WorkflowStage();
stage.WorkflowNextStages = new List<WorkflowNextStage>
{
new SuccessStage() {NextStage = stage},
new FailureStage() {NextStage = stage}
};
workflow.Stages = new List<WorkflowStage> {stage};
ctx.Workflows.Add(workflow);
ctx.SaveChanges();
}
Setting the 'new SuccessStage' above to a different stage works just fine.
I am a bit stumped on this one...anyone have any ideas?
Do you have a foreach loop where you are iterating through the collection you are modifying? If so this may cause this error.