I'm using EF Core and Automapper to update my entities. I have an entity called "Aluno", which has a relationship to "Endereco". Here is the code:
public class Aluno : ApplicationUser{
...
public Endereco Endereco { get; set; }
...
}
public class AlunoViewModel : UserViewModel
{
public int Id { get; set; }
public EnderecoViewModel Endereco { get; set; }
}
public class Endereco{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get;set; }
...
}
public class EnderecoViewModel {
public int Id { get;set; }
...
}
The automapper configuration:
config.CreateMap<Aluno, AlunoViewModel>().ReverseMap();
config.CreateMap<Endereco, EnderecoViewModel>().ReverseMap();
AlunosController:
[HttpPut]
public JsonResult Put([FromBody]AlunoViewModel vm)
{
if (ModelState.IsValid)
{
var aluno = _context.Alunos
.Single(o => o.Id == vm.Id);
Mapper.Map(vm, aluno);
_context.SaveChanges();
Response.StatusCode = (int)System.Net.HttpStatusCode.OK;
return Json(true);
}
Response.StatusCode = (int)System.Net.HttpStatusCode.BadRequest;
return Json(false);
}
When I try to update an "Aluno" entity, I'm getting an exception on context.SaveChanges():
Cannot insert explicit value for identity column in table 'Enderecos'
when IDENTITY_INSERT is set to OFF.
But i'm not trying to insert a new "Endereco", just update the entity "Aluno", and maybe update the "Endereco", which is loaded correctly inside the var aluno.
Am I doing something wrong?
Try the following syntax:
Mapper
.CreateMap<Aluno, AlunoViewModel>()
.ForMember(
dest => dest.Endereco,
opt => opt.MapFrom(src => src.EnderecoViewModel)
);
Related
Suppose I have following entities
public abstract class BaseEntity {
public Guid Id { get;set; }
public string Prop1 { get;set; }
public long Prop2 { get;set; }
public byte Type_Id { get;set; }
}
public class Type1 : BaseEntity { }
public class Type2 : BaseEntity { }
public class Type3 : BaseEntity {
public long? Prop3 { get;set; }
}
And following context mapping:
builder.ToTable("Entities").HasDiscriminator(a => a.Type_Id)
.HasValue<Type1>((byte)Types.Type1)
.HasValue<Type2>((byte)Types.Type2)
.HasValue<Type3>((byte)Types.Type3);
// in DbContext
public DbSet<BaseEntity> Entities { get; set; }
I want to create get IQueryable from DB (all records) Type1 and Type2 will have null in Prop3 ,
I do the following:
public DbSet<BaseEntity> DBSet { get;set; }
private static readonly MethodInfo FromSqlMethodInfo = typeof(RelationalQueryableExtensions).GetTypeInfo().GetDeclaredMethods("FromSql").Single(mi => mi.GetParameters().Length == 3);
public IQueryable<Type3> GetEntities(IEnumerable<Guid> ids) {
RawSqlString sql = #"select [Id]
,[Prop1]
,[Prop2]
,[Prop3]
,[Type_Id]
from [dbo].[Entities] where Id in (select item from #Ids)";
var ids = new SqlParameter("#Ids", SqlDbType.Structured);
ids.TypeName = typeof(Guid).Name.ToLowerInvariant() + "_item_list";
ids.Direction = ParameterDirection.Input;
ids.Value = CreateItemList(tpIds);
var param = new object[] { ids };
var conversion = from s in DBSet select (Type3)s;
var result = conversion.Provider.CreateQuery<Type3>(Expression.Call(null, FromSqlMethodInfo.MakeGenericMethod(typeof(Type3)), conversion.Expression,
Expression.Constant(sql), Expression.Constant(param)));
return result;
}
var query = GetEntities(someIds);
var result = query.OrderBy(m => m.Type_Id).Skip(skip).Take(take).ToList();
And query executes successfully, but when ToLIst is called, Exception is thrown:
Unable to cast object of type 'Type1' to type 'Type3', which is quite expected, since we not telling how it should be converted...so the question is: can such trick be done with EF Core ?
Ok, so for now only possible solution is to create one more entity with all properties required,
public class NewEntity {
public Guid Id { get;set; }
public string Prop1 { get;set; }
public long Prop2 { get;set; }
public byte Type_Id { get;set; }
public long? Prop3 { get;set; }
}
add new DbSet to context
public DbSet<NewEntity> NewEntities { get; set; }
Map it to old table and update context configuration properly,
to avoid redundant columns generation (in this scenario EF will probaly
try to add new columns with names NewEntity_Prop1 , NewEntity_Prop2 etc..
as well as Foreign keys and indexes
public override void Configure(EntityTypeBuilder<NewEntity> builder) {
builder.ToTable("Entities");
builder.Property(m => m.Prop1).HasColumnName("Prop1");
builder.Property(m => m.Prop2).HasColumnName("Prop2");
builder.Property(m => m.Type_Id).HasColumnName("Type_Id");
builder.HasOne<BaseEntity>().WithOne().HasForeignKey<NewEntity>(e => e.Id);
}
Or create View
public DbQuery<NewEntity> NewEntities { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Query<NewEntity>().ToView("NewEntities");
base.OnModelCreating(modelBuilder);
}
You can use implicit Overload Cast Operators. Here is a link to help you.
I am attempting to save date in multiple tables with a one-to-many relationship in using EF Core. When I do, I get this error:
InvalidOperationException: The instance of entity type 'OrganizationGroupEntity' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
Here is my code:
Request model:
public class Organization
{
public Organization()
{ }
public Organization(OrganizationEntity organizationEntity, List<OrganizationGroupEntity> organizationGroupEntities)
{
Id = organizationEntity.Id;
Name = organizationEntity.Name;
Groups = ToList(organizationGroupEntities);
}
public int Id { get; set; }
public string Name { get; set; }}
public List<OrganizationGroup> Groups { get; set; }
private List<OrganizationGroup> ToList(List<OrganizationGroupEntity> organizationGroupEntities)
{
return organizationGroupEntities.Select(
entity => new OrganizationGroup(entity)
).ToList();
}
}
public class OrganizationGroup
{
public OrganizationGroup()
{ }
public OrganizationGroup (OrganizationGroupEntity entity)
{
Id = entity.Id;
Group = entity.Group;
}
public int Id { get; set; }
public string Group { get; set; }
}
Entity models:
public class OrganizationEntity
{
public OrganizationEntity()
{ }
public OrganizationEntity(Organization model)
{
Id = model.Id;
Name = model.Name;
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
}
public class OrganizationGroupEntity
{
public OrganizationGroupEntity()
{ }
public OrganizationGroupEntity(int organizationId, OrganizationGroup model)
{
Id = model.Id;
OrganizationId = organizationId;
Group = model.Group;
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int OrganizationId { get; set; }
public string Group { get; set; }
}
dbContext:
public DbSet<OrganizationEntity> Organizations { get; set; }
public DbSet<OrganizationGroupEntity> OrganizationGroups { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<OrganizationEntity>()
.ToTable("Organizations", "dbo");
modelBuilder.Entity<OrganizationGroupEntity>()
.ToTable("OrganizationGroups", "dbo");
}
repository:
public async Task<Organization> UpdateOrganization(Organization request)
{
// Get the org entity
var organizationEntity = new OrganizationEntity(request);
// get the org groups entities
var groupEntities = request.Groups
.Select(
group => new OrganizationGroupEntity(request.Id, group)
).ToList();
// Get the group entities to remove
var oldEntities = GetOrganizationGroups(request.Id);
var entitiesToRemove = new List<OrganizationGroupEntity>();
foreach (var oldEntity in oldEntities.Result)
{
if (!groupEntities.Any(e => e.Id == oldEntity.Id))
{
entitiesToRemove.Add(oldEntity);
}
}
using (var transaction = _context.Database.BeginTransaction())
{
_context.Organizations.Update(organizationEntity);
_context.OrganizationGroups.UpdateRange(groupEntities); // <-- Fails here
_context.OrganizationGroups.RemoveRange(entitiesToRemove);
await _context.SaveChangesAsync();
transaction.Commit();
}
return request;
}
private async Task<IEnumerable<OrganizationGroupEntity>> GetOrganizationGroups(int organizationId)
{
return await _context.OrganizationGroups
.Where(e => e.OrganizationId == organizationId)
.OrderBy(e => e.Order)
.ToListAsync();
}
It turns out when I was getting the current groupEntities in order to fins out what to remove I was initiating tracking on that table. Adding AsNoTracking() to GetOrganizationGroups solved my issue. Like so:
private async Task<IEnumerable<OrganizationGroupEntity>> GetOrganizationGroups(int organizationId)
{
return await _context.OrganizationGroups
.AsNoTracking()
.Where(e => e.OrganizationId == organizationId)
.OrderBy(e => e.Order)
.ToListAsync();
}
I have Student entity which has Class (Class can have many students)
This is my Student.cs and StudentDto.cs
public partial class Students
{
public int Id { get; set; }
public string FullName { get; set; }
public int? FkClass { get; set; }
}
public class StudentsDTO
{
public int Id { get; set; }
public string FullName { get; set; }
public int FkClass { get; set; }
public int FkClassNavigationId { get; set; }
public string FkClassNavigationTitle { get; set; }
}
When I am creating new student and select class id from the dropdown, instead of using that class, new class is created (with null values and random ID).
I am not sure where is the best way to fix this mistake? Is it the problem in Automapper, context.cs class or my add method is wrong itself?
In my service I do this:
public Students PostStudent(StudentsDTO item)
{
var sub = _mapper.Map<Students>(item);
_repository.PostStudent(sub);
return sub;
}
and In the repository I do the following:
public void PostStudent(Students c)
{
this.museumContext.Add(c);
this.museumContext.SaveChanges();
}
Automapper.cs:
CreateMap<Students, StudentsDTO>()
.ReverseMap();
and context.s
modelBuilder.Entity<Students>(entity =>
{
entity.ToTable("students");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.FkClass).HasColumnName("fkClass");
entity.Property(e => e.FullName)
.HasColumnName("fullName")
.HasMaxLength(255);
entity.HasOne(d => d.FkClassNavigation)
.WithMany(p => p.Students)
.HasForeignKey(d => d.FkClass)
.HasConstraintName("Include");
});
Can someone help me understand how to fix this?
The weird thing is if I add var a = _classesService.GetClassById(s.FkClass); to my controller it works fine. It's not that I'm assigning what I retrieve or anything.
[HttpPost]
[AllowAnonymous]
public IActionResult PostStudent([FromBody] StudentsDTO s)
{
if (!this.ModelState.IsValid)
{
return this.BadRequest(ModelState);
}
try
{
var a = _classesService.GetClassById(s.FkClass);
var sub = this._service.PostStudent(s);
return Ok();
}
catch (Exception err)
{
return BadRequest(err);
}
}
I have used the Entity Framework Core and update some object with relation to another object.
I have a following entities:
Client:
public class Client
{
public int Id { get; set; }
public string ClientId { get; set; }
public string ClientName { get; set; }
public List<ClientScope> Scopes { get; set; }
}
ClientScope:
public class ClientScope
{
public int Id { get; set; }
public string Scope { get; set; }
public Client Client { get; set; }
}
OnModelCreating:
modelBuilder.Entity<Client>((Action<EntityTypeBuilder<Client>>) (client =>
{
client.ToTable<Client>(storeOptions.Client);
client.HasKey((Expression<Func<Client, object>>) (x => (object) x.Id));
client.Property<string>((Expression<Func<Client, string>>) (x => x.ClientId)).HasMaxLength(200).IsRequired(true);
client.HasIndex((Expression<Func<Client, object>>) (x => x.ClientId)).IsUnique(true);
client.HasMany<ClientScope>((Expression<Func<Client, IEnumerable<ClientScope>>>) (x => x.AllowedScopes)).WithOne((Expression<Func<ClientScope, Client>>) (x => x.Client)).IsRequired(true).OnDelete(DeleteBehavior.Cascade);
}));
modelBuilder.Entity<ClientScope>((Action<EntityTypeBuilder<ClientScope>>) (scope =>
{
scope.ToTable<ClientScope>(storeOptions.ClientScopes);
scope.Property<string>((Expression<Func<ClientScope, string>>) (x => x.Scope)).HasMaxLength(200).IsRequired(true);
}));
I would like update the ClientScope for specific Client.Id for example Id = 1.
I have tried use this way:
public void UpdateClientScope(ClientScope scope){
_dbContext.ClientScope.Update(scope);
_dbContext.SaveChanges();
}
var scope = new ClientScope() { Client = new Client{Id = 1}, Id = 1, Scope = "Test Scope" }
UpdateClientScope(scope);
But this way try to update the Client as well. I want to update only ClientScope and specify ClientId which it is stored on my form.
What is the best way how update ClientScope above?
I have tried to implement the BaseRepository which I want to implement for every entity something like this:
public class BaseRepository<TDbContext, TEntity, TPrimaryKey> : IBaseRepository<TDbContext, TEntity, TPrimaryKey>
where TEntity : class
where TDbContext : DbContext
{
public virtual DbSet<TEntity> Table => _dbContext.Set<TEntity>();
private readonly TDbContext _dbContext;
public BaseRepository(TDbContext dbContext)
{
_dbContext = dbContext;
}
public virtual async Task<TEntity> UpdateAsync(TEntity entity)
{
Table.Update(entity);
await _dbContext.SaveChangesAsync();
return entity;
}
}
But I don't know - How correctly specify the update method for entities like this?
Thank you for any suggestion.
If you never plan to add or modify a related entity via your repository methods you can simply set all other entities' state to EntityState.Unchanged, e.g.:
public virtual async Task<TEntity> UpdateAsync(TEntity entity)
{
Table.Update(entity);
foreach( var entry in _dbContext.ChangeTracker.Entries() )
{
if( entry.Entity != entity )
{
entry.State = EntityState.Unchanged;
}
}
await _dbContext.SaveChangesAsync();
return entity;
}
Alternatively, attach related entities as unchanged before calling the repo method(s). Perhaps create a repo method to return such an entity:
public TEntity GetEntityPlaceholder( int id )
{
var entity = new TEntity() { Id = id };
_dbContext.Attach( entity );
return entity;
}
I prefer having the FK properties available, myself:
public class ClientScope
{
public int Id { get; set; }
public string Scope { get; set; }
public Client Client { get; set; }
// FK, use [ForeignKey( "Client" )] if you wish
public int ClientId { get; set; }
}
// or use FluentAPI
modelBuilder.Entity<ClientScope>()
.HasRequired( cs => cs.Client )
.WithMany( c => c.Scopes )
// specify foreign key
.HasForeignKey( cs => cs.ClientId );
// now can specify a client by setting the ClientId property
var scope = new ClientScope()
{
ClientId = 1,
Id = 1,
Scope = "Test Scope",
}
UpdateClientScope(scope);
As mentioned in the comments (thanks, Ivan), EF needs to 'know about' the object you want to update.
Sorry, I don't have anything to hand to test this with, but your UpdateClientScope method should look something like this:
public void UpdateClientScope(ClientScope scope){
// Get the existing object from the DB
ClientScope dbScope = _dbContext.ClientScope.FirstOrDefault(x => x.Id == scope.Id);
// Test it was in DB
if (dbScope != null)
{
// Update the database object
dbScope.Scope = scope.Scope;
dbScope.Client = scope.Client;
// SaveChanges works on dbScope
_dbContext.SaveChanges();
}
else
{
// Object not found, some error processing
}
}
I do not understand where is a problem ? through DBContext object you can reach any class object than object's property, than just change a value of property with simple assign operator and just DbContext.SaveChanges() your changes will be updated in Database column with no problem at all. If you want separate Update() method for changes you can do it with couple of code lines or more complex way with C# Reflection possibility but i can't imagine why would you need this ?! If i missed something tell me more. Good luck !!!!
i think your entities false because your ClientScope has not clientId (Foreign Key).
public string ClientId { get; set; }
I have a viewModel that contains two classes...
public class vwbooking
{
public booking bookings { get; set; }
public IEnumerable<trace> traces { get; set; }
}
Booking and trace are entities in an edmx.
I want to update the data in these two class with one call to save.
This is what I've tried, along with several other unsuccessful "shot-in-the-dark" variants...
public ActionResult Edit(vwbooking vwbooking)
{
if (ModelState.IsValid)
{
DbEntityEntry<vwbooking> entry = db.Entry(vwbooking);
entry.Property(e => e.bookings.firstname).IsModified = true;
db.SaveChanges();
}
}
I get the following error when calling the save method...
The entity type vwbooking is not part of the model for the current context.
The GET method loads successfully. The post is where I'm having trouble. This is the GET method...
public ActionResult Edit(int id = 0)
{
booking booking = db.bookings.Find(id);
var viewModel = new vwbooking();
viewModel.bookings = booking;
viewModel.traces = (from l in db.traces where l.bookingid == booking.bookingid select l);
return View(viewModel);
}
This is my db context class
public class salesContext : DbContext
{
public salesContext() : base()
{
Configuration.LazyLoadingEnabled = true;
}
public salesContext(string Connection) : base(Connection)
{
Configuration.LazyLoadingEnabled = true;
}
public DbSet<booking> bookings { get; set; }
public DbSet<trace> traces { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<booking>().HasKey(e => e.bookingid);
modelBuilder.Entity<trace>().HasKey(e => e.traceid);
}
}
The code for update your model is:
db.Attach(vwbooking.bookings)
db.ObjectStateManager.ChangeObjectState(vwbooking.bookings, System.Data.EntityState.Modified)
vwbooking.traces.ToList().ForEach(
t =>
{
db.Attach(t);
db.ObjectStateManager.ChangeObjectState(t, System.Data.EntityState.Modified);
}
);
db.SaveChanges();
try this code in the Edit Controller