how to edit many-to-many related data in EF? - c#

I have a many-to-many relationship, I'm using asp net core, ef, and blazor and I want to edit my model that has that relationship, but I don't know how to do it, with models without a many-to-many relationship I haven't had problems editing, but with this type of relationship I don't know how to do it, previously I used automapper but in this project I can't use it so I map the data manually, also I get this error: :
Value cannot be null. (Parameter 'key')
this is my model:
public class Level
{
public int LevelId { get; set; }
public string Name { get; set; }
public virtual List<LevelInstallationType> LevelInstallationTypes { get; set; } = new List<LevelInstallationType>();
}
public class InstallationType
{
public int InstallationTypeId { get; set; }
public string Name { get; set; }
public virtual List<LevelInstallationType> LevelInstallationTypes { get; set; }
}
public class LevelInstallationType
{
public int LevelId { get; set; }
public int InstallationTypeId { get; set; }
public virtual Level Level { get; set; }
public virtual InstallationType InstallationType { get; set; }
}
[HttpPut]
public async Task<ActionResult> Put(Level level)
{
var oldDB = await _context.Level
.FirstOrDefaultAsync(x => x.Id == level.Id);
oldDB.Name = level.Name;
oldDB.LevelInstallationTypes = level.LevelInstallationTypes;
context.Entry(oldDB).State = EntityState.Modified;
await _context.SaveChangesAsync();
return NoContent();
}

It seems you are trying to add null somewhere. Debug and check the data.

Related

EF Core: Cannot insert explicit value for identity column in table when IDENTITY_INSERT is set to OFF on one to many

I am using EF Core together with ASP NET Core for my server, and when I am trying to update an existing value in the database I receive the following error:
Cannot insert explicit value for identity column in table 'TeambuildingType' when IDENTITY_INSERT is set to OFF.
What I am doing is this:
creating a Teambuilding element, with the foreign key for the TeamBuildingTypeId set to NULL initially
creating two TeambuildingType directly from the SQL Management Studio using INSERT INTO.... (the Id is auto incremented for both the Teambuilding and TeambuildingType)
trying to update the existing Teambuilding by adding either the TeambuildingTypeId like this: team.TeambuildingTypeId = 1 or team.Type = (object fetched from the database in the same context)
receiving the error from above in a catch
Here is my code:
TeamBuilding.cs
public class TeamBuilding
{
[Key]
public int Id { get; set; }
public string Title { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public double? TargetBudget { get; set; }
public TeambuildingStatus? Status { get; set; }
public int? TeambuildingTypeId { get; set; }
public virtual TeambuildingType Type { get; set; }
public int? LocationId { get; set; }
public virtual Location Location { get; set; }
public virtual ICollection<Participant> Participants { get; set; }
public virtual ICollection<Room> Rooms { get; set; }
public int TimePollId { get; set; }
public virtual TimePoll TimePoll { get; set; }
}
TeambuildingType.cs
public class TeambuildingType
{
[Key]
public int Id { get; set; }
public string Type { get; set; }
public virtual ICollection<TeamBuilding> Teambuildings { get; set; }
}
TeamBuildingForUpdateDto.cs
public class TeamBuildingForUpdateDto
{
[Required]
public int Id { get; set; }
public string Title { get; set; }
[DataType(DataType.DateTime)]
public DateTime StartDate { get; set; }
[DataType(DataType.DateTime)]
public DateTime EndDate { get; set; }
public LocationViewModel Location { get; set; }
public TeambuildingStatus Status { get; set; }
public double TargetBudget { get; set; }
public TeamBuildingTypeDto Type { get; set; }
}
The update controller method:
[HttpPut]
public IActionResult UpdateTeamBuilding([FromBody]TeamBuildingForUpdateDto teamBuildingForUpdateDto)
{
try
{
var existingTeamBuilding = _service.GetByID(teamBuildingForUpdateDto.Id);
if (existingTeamBuilding == null)
{
return NotFound("There is no team buiding with such an ID");
}
_service.UpdateTeamBuilding(teamBuildingForUpdateDto);
return Ok();
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
The service method:
public TeamBuildingForUpdateDto UpdateTeamBuilding(TeamBuildingForUpdateDto teamBuildingDto)
{
var teamBuilding = _repositoryTeam.GetByID(teamBuildingDto.Id);
var type = _repositoryType.GetByID(teamBuildingDto.Type.Id);
Mapper.Map(teamBuildingDto.Type, type);
Mapper.Map(teamBuildingDto, teamBuilding);
teamBuilding.Type = type;
//OR
//teamBuilding.TeambuildingTypeId = type.Id;
//Neither from above works
_repositoryTeam.Edit(teamBuilding);
_repositoryTeam.Commit();
return teamBuildingDto;
}
Context using the Fluent API:
modelBuilder.Entity<Models.TeamBuilding>()
.HasOne(t => t.Type)
.WithMany(ty => ty.Teambuildings)
.HasForeignKey(t => t.TeambuildingTypeId);
modelBuilder.Entity<TeambuildingType>().ToTable("TeambuildingType");
modelBuilder.Entity<Models.TeamBuilding>().ToTable("TeamBuilding");
public DbSet<TeambuildingType> TeambuildingTypes { get; set; }
public DbSet<Models.TeamBuilding> TeamBuildings { get; set; }
I also don't receive this error on those models only, I receive the same thing on anything that uses FK and on where I try to insert a new value in there.
The relationship is one to many between the TeamBuilding and the TeambuildingType
What am I doing wrong?
I fixed the problem. As per mcbowes suggestion I checked the AutoMapper and what I send from the server, and I saw that I was trying to assign a TeamBuildingType in my TeamBuilding Type field, then trying to do the update.
I fixed the problem by not assigning any TeamBuildingType to the Type field (making it being null) and assigning only the TeamBuildingType primary key to the TeambuildingTypeId field. Now it does the update.
Thanks mcbowes for the suggestion.

Retrieving data from multiple tables through Get() method in web API

I have just started with web API and got my first Get method successfully implemented. I was able to retrieve the data and show it to the client. Now I have to retrieve data from two tables through a single Get method which I am not able too. Here's my code for retrieving data from a single table.
public HttpResponsemessage Get(string Login, string Password)
{
using (Accord_BMHEntities entities = new Accord_BMHEntities())
{
Login = Login.Trim();
EncryptDecrypt EncryptDecryptObj = new EncryptDecrypt();
string EncryptedPassword =
EncryptDeccrypt.Encrypt(Login.Trim().ToUpper(), Password);
var userLogin = entities.ITPLUsers.firstOrDefault(e => e.Login == Login
& e.Password == EncryptedPassword
if (UserLogin == null)
{
return Request.CreateErrorResponse(HttpStatusCode.NotFound)
}
else
{
return Request.CreateResponse(HttpStatusCode.OK , UserLogin)
}
}
}
EmpMaster.cs
public partial class EmpMaster
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", n
CA2212:DoNotCallOverridableMethodsInConstructors")]
Public EmpMaster()
{
this.EmpPersonal = new HashSet<EmpPersonal>();
}
Public int EmployeeID { get; set; }
Public int DivisionId { get; set; }
Public int ResumeId { get; set; }
Public int GroupId { get; set; }
Public int DepartmentId { get; set; }
Public int WorkplaceId { get; set; }
Public int DesignationId { get; set; }
Public int Code { get; set; }
Public int DesignationId { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", n
CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICOllection<EmpPersonal> EmpPersonals { get; set; }
}
EmpPersonal.cs
Public partial class EmpPersonal
{
Public int EmployeeID { get; set; }
Public int DivisionID { get; set; }
Public short Gender { get; set; }
Public short BloodGroup { get; set; }
Public string FlatNo { get; set; }
Public string Premises { get; set; }
Public string Street { get; set; }
Public string Area { get; set; }
Public string City { get; set; }
Public string StateId { get; set; }
Public string CountryId { get; set; }
Public virtual EmpMaster EmpMaster { get; set; }
}
Please note : there are many more properties in both the class. Just to save time i have mentioned some of it.
If you have modeled the relation between the entities, you could use the Entity Framweork Include method (for eager loading) or Load method (for lazy loading).
Docs here: Entity Framework Loading Related Entities
Otherwise you could return an anonymous type:
var userLogin = entities.ITPLUsers.firstOrDefault(e => e.Login == Login
& e.Password == EncryptedPassword;
var empPersonal = entities.EmpPersonal.Where(....your condition...);
if (UserLogin == null)
{
return Request.CreateErrorResponse(HttpStatusCode.NotFound)
}
else
{
return Request.CreateResponse(HttpStatusCode.OK,new {userlogin = UserLogin, empPersonal = empPersonal});
}
I assume you want user address from Address entity along with user data. You have 2 options here. You can either use linq and join to get all related data with one query or you can get user and address datas one by one. In the end, you need to consolidate the data and return one result set since you want to return all of them in a single response.
You obviously need a resultModel for your endpoint.
Like;
public class UserResultModel
{
//Properties from User entity
public int Id { get; set; }
public string Username { get; set; }
//Properties from Address entity
public string City { get; set; }
}
You need to fill this resultModel and return it.
Irrelevant suggestion: I suggest not to check user's authentication like that. MVC has a really nice feature called Filters(Authorization Filters for more detailed)
Try Linq with joins by matching keys. You will get an example from msdn site.Msdn site

ASP.NET MVC How to access Entity Framework generated foreign key?

Song has a many to many relationship with it's self. I store these ids in a class called SimilarVersion with both id columns.
public class Song
{
public int Id { get; set; }
public string AudioName { get; set; }
public string ArtistName { get; set; }
...
public virtual ICollection<SimilarVersion> SimilarVersions { get; set; } = new List<SimilarVersion>();
}
public class SimilarVersion
{
public int Id { get; set; }
public int? Song_Id1 { get; set; }
}
View Models:
public class SongDto
{
public int Id { get; set; }
public string AudioName { get; set; }
public string ArtistName { get; set; }
...
public ICollection<SimilarSongDto> SimilarSongDtos { get; set; } = new List<SimilarSongDto>();
}
public class SimilarSongDto
{
public int Id { get; set; }
public string AudioName { get; set; }
public string ArtistName { get; set; }
...
}
The SimilarVersion table in my database now has Id,Song_Id,Song_Id1, as EF has generated Song_Id. How do I get to use that EF generated column in my code though?
_similarVersionService.GetSimiliarVersion().Song_Id will give me an error because there is no property in the class called that. I could manually add it to the class like I have done with Song_Id1 and remove the collection from the other class but I think I must be doing something wrong. Also please tell me if there is a better way of mapping this.
I tried adding public int Song_Id { get; set; } and it just made another column in my table called Song_Id2.
public ActionResult Song(int id)
{
//Map the domainModel to songViewModel
var songDto = Mapper.Map<Song, SongDto>(_songService.GetSong(id));
//Get all of the songs where the id == the Song_Id column in similar version table
var songs = _songService.GetSongs().ToList()
.Where(x => x.SimilarVersions.Any(z => z.Song_Id == songDto.Id))
.ToList(); //z.Song_Id no definition found
//Map the Song domain to SimilarSong ViewModel and assign it to the songDto to be passed to the view
songDto.SimilarSongDtos = Mapper.Map<ICollection<Song>, ICollection<SimilarSongDto>>(songs);
return View(songDto);
}
Edit. Trying to add to a row based on Admir answer:
var songToUpload = new Song
{
AudioName = uploadSongDtos[i].AudioName.Trim(),
ArtistName = uploadSongDtos[i].ArtistName,
};
foreach (var compareAgainstString in _songService.GetSongs().ToDictionary(x => x.Id, x => x.AudioName))
{
var score = SearchContext.Levenshtein.iLD(songToUpload.AudioName, compareAgainstString.Value);
//Don't add the current song
if (score < 50 && songToUpload.Id != compareAgainstString.Key)
songToUpload.SimilarVersionsWhereSimilar.Add(new SimilarVersion { SimilarId = compareAgainstString.Key });
}
Both OriginalId and SimilarId are assigned to whatever the id of songToUpload.Id is given the relationship we defined in models, which is correct for OriginalId but it is also overriding my custom set SimilarId above. How can I stop this?
You can take this approach:
public class Song
{
public int Id { get; set; }
public string ArtistName { get; set; }
public virtual IList<Similarity> SimilaritiesWhereOriginal { get; set; }
public virtual IList<Similarity> SimilaritiesWhereSimilar { get; set; }
}
public class Similarity
{
public int Id { get; set; }
public int OriginalId { get; set; }
public virtual Song Original { get; set; }
public int SimilarId { get; set; }
public virtual Song Similar { get; set; }
}
public class ApplicationDbContext : DbContext
{
public DbSet<Song> Songs { get; set; }
public DbSet<Similarity> Similarities { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Song>().HasMany(x => x.SimilaritiesWhereOriginal).WithRequired(x => x.Original).WillCascadeOnDelete(false);
modelBuilder.Entity<Song>().HasMany(x => x.SimilaritiesWhereSimilar).WithRequired(x => x.Similar).WillCascadeOnDelete(false);
base.OnModelCreating(modelBuilder);
}
}
Similarity class shows relationship between "original" song and "similar" song. This class replaces EF auto-generated table with your own many-2-many relationship table that you can access from the code.
It is likely the ID is actually generated by your Store. Call Context.SaveChanges() to create it, then read the ID.

Set property from another table with no relationship Entity Framework

I'm trying to set a property in my entity based from another entity that has no relationship one to the other. The entity sets the properties from the database and I want one of the properties to be set from another table in database, but I can't get to the bottom of it.
For example,
public class First {
[Key]
public int ProdId { get; set; }
public string Supplier { get; set; }
public virtual ICollection<Log> Logs { get; set; }
[NotMapped]
public bool IsSavedForLater
{
get
{
return Logs.Where(l =>
{
var content = l.LogContent.JsonStringToObject<History>();
return (content.ProdId == ProdId && l.TableName == "Condition");
}).Any();
}
}
}
As you can see the property IsSavedForLater is [NotMapped] and I want this property to get set from the Logs,
Here is the log Entity,
public class Log
{
[Key]
public int LogId { get; set; }
public string LogContent { get; set; }
public string TableName { get; set; }
public DateTime LogDate { get; set; }
public string BlameName { get; set; }
public bool? Deleted { get; set; }
}
is it possible to navigate like this without any database relationship?
Guys I found my solution after a huge amount of time figuring it out.
What I did is, I added first another property in my First class like this
public class First {
[Key]
public int ProdId { get; set; }
public string Supplier { get; set; }
public string TableName { get; set; }
public virtual ICollection<Log> Logs { get; set; }
}
As you can see the First class has a new property TableName that is also in my Logs class like this,
public class Log
{
[Key]
public int LogId { get; set; }
public string LogContent { get; set; }
public string TableName { get; set; }
public DateTime LogDate { get; set; }
public string BlameName { get; set; }
public bool? Deleted { get; set; }
}
Then I added in the db context class a model builder like this,
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<SmuForecasting>().HasKey(x => new { x.TableName }).HasMany(x => x.PsLogs).WithOptional().HasForeignKey(x => new { x.TableName });
}
That worked perfectly good for me but with side effects, it replaces the original key that was set to it in the beginning, so I ended up doing a total different approach.

Add a navigation property to a plain object

So I have a viewmodel in my application that looks like:
public class CountryVM
{
[Key]
public int CountryID { get; set; }
[ForeignKey("Country_CountryID")]
public virtual ICollection<OrganisationVM> Organisations { get; set; }
public CountryVM(Country country)
{
if (country == null) return;
this.CountryID = country.CountryID;
}
}
That is seeded by the class:
public class Country
{
[Key]
public int CountryID { get; set; }
public virtual ICollection<Organisation> Organisations { get; set; }
}
The other relevant classes are:
public class Organisation
{
[Key]
public int OrganisationId { get; set; }
[Required]
public virtual Country Country { get; set; }
}
public class OrganisationVM
{
[Key]
public int OrganisationId { get; set; }
[ForeignKey("Country_CountryID")]
public virtual CountryVM Country { get; set; }
public int? Country_CountryID { get; set; }
}
Now Organisation and Country are tables in my database and OrganisationVM is a view. CountryVM exists only in the application and at the moment EF tries to create a table in my database 'dbo.CountryVMs'.
Is there anyway to hook it up like this so I can use a navigation property in CountryVM without it creating tables?
The reason I want to do this is:
I have this code in my web.api controller at the moment:
// GET: odata/Country
[EnableQuery]
public IQueryable<CountryVM> Get()
{
var a = db.Countries.ToList().Select<Country, CountryVM>(c => new CountryVM(c)).AsQueryable();
return a;
}
I was hoping that odata would allow me to use expand and select to select the countryVMs Organisations but it hasn't so far.

Categories