UserManager not updating custom Identity User properly - c#

I'm using Using aspnet Identity + OpenIddict to do authentication on top of EF Core.
I extended the IdentityUser class to include two lists; StudyTags and ExpertTags, along with a few other properties:
public class ApplicationUser : IdentityUser
{
public ApplicationUser()
{
StudyTags = new List<Tag>();
ExpertTags = new List<Tag>();
}
public string FirstName { get; set; }
public string LastName { get; set; }
public string Location { get; set; }
public List<Tag> StudyTags { get; set; }
public List<Tag> ExpertTags { get; set; }
}
This is all well and good, however, when I want to update these lists and save them, the update only seems to persist in a certain scope. For instance, if I call the below API call twice, the first time adds the tag to the proper list, and the second time logs a message saying it's already been added. I can inspect the user and verify that the tag is in the list as expected.
[Authorize(ActiveAuthenticationSchemes = OAuthValidationDefaults.AuthenticationScheme)]
[HttpPost("add/tag/{tagType}/{tagName}")]
public async Task<IActionResult> AddTagToUser(int tagType, string tagName)
{
var user = await _userManager.GetUserAsync(User) as ApplicationUser;
if(user == null)
{
_logger.LogError("User not found");
return BadRequest();
}
TagType type = (TagType)tagType;
var tag = _tagService.GetTagByName(tagName);
if(tag == null)
{
_logger.LogError("No tag corresponding to tagName '{0}'", tagName);
return BadRequest();
}
if(type == TagType.Study)
{
if(user.StudyTags.Contains(tag))
{
_logger.LogInformation("{0} already assigned to {1}", tag.ToString(), user.ToString());
}
else
{
_logger.LogInformation("{0} added to {1}", tag.ToString(), user.ToString());
user.StudyTags.Add(tag);
}
}
else if(type == TagType.Expert)
{
if(user.ExpertTags.Contains(tag))
{
_logger.LogInformation("{0} already assigned to {1}", tag.ToString(), user.ToString());
}
else
{
_logger.LogInformation("{0} added to {1}", tag.ToString(), user.ToString());
user.ExpertTags.Add(tag);
}
}
else
{
_logger.LogError("No tagType corresponding to {0}", tagType);
return BadRequest();
}
var result = await _userManager.UpdateAsync(user);
if(result.Succeeded)
{
return Ok(result);
}
else
{
_logger.LogError("Updating Tag failed");
return BadRequest();
}
}
However, if I just try to do a Get of the same user, the StudyTags and ExpertTags lists are empty.
[Authorize(ActiveAuthenticationSchemes = OAuthValidationDefaults.AuthenticationScheme)]
[HttpGet]
public async Task<IActionResult> Get()
{
var user = await _userManager.GetUserAsync(User);
if(user == null)
{
_logger.LogError("User not found");
return BadRequest();
}
return Ok(user);
}
Am I doing something incorrectly? Any help would be appreciated!

In your _userManager.GetUserAsync method you have to write the code to eagerly load the StudyTags like below:
context.User.Include(x => x.StudyTags).Include(x => x.ExpertTags);
If your property is virtual, then it will only be loaded when you access the property (lazy loading). If your property is not virtual, then it should be loaded when object holding the property is loaded.
If you want to turn off lazy loading globally you can include this in your context constructor:
this.Configuration.LazyLoadingEnabled = false; // careful though keep reading
The above switch has a global effect so if not careful it can cause performance issues since the whole object graph will be loaded every time, even though you may not need it.
To turn off lazy loading for a single property, make it NON virtual.

Your properties should be marked as virtual. If the records exist in your database but just aren't being fetched when you run the GET method and fetch data for the user, then it's because of lazy loading. The virtual keyword will allow them to be populated when you access the user object.
This is what you want:
public virtual List<Tag> StudyTags { get; set; }
public virtual List<Tag> ExpertTags { get; set; }
UPDATE:
So it looks like what's required is a M:M relationship. Since EF Core doesn't support the auto-creation of the junction class, it'll have to be made explicitly. So in the end, your models must look like:
public class ApplicationUser : IdentityUser
{
//Other Properties
public ICollection<Tag> StudyTags { get; set; }
public ICollection<Tag> ExpertTags { get; set; }
}
public class Tag
{
//Other Properties
public ICollection<ApplicationUser> Users { get; set; }
}
public class UserTags
{
public int UserId { get; set; }
public ApplicationUser User { get; set; }
public int TagId { get; set; }
public Tag Tag { get; set; }
}
And your context needs to be updated to include:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserTag>()
.HasKey(x => new { x.User, x.TagId });
modelBuilder.Entity<UserTag>()
.HasOne(x => x.User)
.WithMany(y => y.UserTags)
.HasForeignKey(x => x.UserId);
modelBuilder.Entity<UserTag>()
.HasOne(x => x.Tag)
.WithMany(y => y.UserTags)
.HasForeignKey(x => x.TagId);
}

Related

How to get navigation properties with [Owned] attribute Tracked in Entity Framework Core

I got EF tracking turned off by default on my project.
I got a DB model class UserModel:
public class UserModel : BaseAdminDbModel
{
[Key]
public int UserId { get; set; }
[NotMapped]
public string UserName { get; set; }
[Required]
public RightsModel R { get; set; }
public BranchUserModel()
{
R = new RightsModel();
}
[ForeignKey("UserId")]
public UserDataModel User { get; set; }
}
RightsModel:
[Owned]
public class RightsModel
{
public bool? RightOne { get; set; }
public bool? RightTwo { get; set; }
public bool? RightN { get; set; }
}
I want to add Owned navigation properties to be tracked before saving db context so every change in RightModel in UserModel will be saved to database (currently its ignoring those fields)
I want to have somewhat like this:
_dbContext.Entry(model).State = EntityState.Modified;
foreach(var navigationRightProperty in _dbContext.Entry(model).Navigations)
{
navigationRightProperty.Load();
}
Result:
Changes to RightModel will be also tracked with the main UserModel entity.
Model tracking is enabled by default for EF Core. So if you use the Include method on R property while making your request and making changes to it, then you call the SaveChanges method all changes will be saved in the database
var id = 1;
var model = _dbContext.UserModel.Include(x => x.R ).FirstOrDefalt(x => x.Id == id)
model.R.RightOne = true;
_dbContext.SaveChanges();
Also you can change your constructor for UserModel like
public UserModel()
{
this.R = new HashSet<RightsModel>();
}
After some time I came up to solve this issue. Drakk L solution is good if you have tracking turned on by default and want to rely on real objects.
I wanted more abstract way to achieve this:
var navigationOwnedProps = _dbContext
.Entry(model)
.Navigations
.Where(e => e.Metadata
.ClrType.CustomAttributes.Any(c => c.AttributeType ==
typeof(OwnedAttribute))).Select(e => e.CurrentValue)
.ToList();
foreach (var nav in navigationOwnedProps)
{
_dbContext.Entry(nav).State = EntityState.Modified;
}

How to add value to a navigation property with Blazor and EF?

I have a reservation and a ship class for the DB with the Ship and Reservations navigation properties,
public class Reservation {
public Reservation() {
Ship = new Ship();
}
public int Id { get; set; }
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
public Ship Ship { get; set; }
}
public class Ship {
public int Id { get; set; }
public string Name { get; set; }
public string Port{ get; set; }
public List<Reservation> Reservations { get; set; }
}
The DBcontext.cs file:
protected override void OnModelCreating(ModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Reservation>().HasOne(r => r.Ship).WithMany(s => s.Reservations);
modelBuilder.Entity<Reservation>().HasOne(u => u.Person).WithMany(r => r.Reservations);
modelBuilder.Entity<Ship>().HasMany(res => res.Reservations).WithOne(s => s.Ship);
}
public DbSet<Reservation> Reservations { get; set; }
public DbSet<Ship> Ships { get; set; }
}
}
The reservationController.cs on the sever side:
[HttpPost]
public async Task<IActionResult> CreateReservation(ReservationGetDTO reservationDTO) {
_context.Reservations.Add(Mapper.Map(reservationDTO, new Reservation()));
await _context.SaveChangesAsync();
return await GetReservations();
}
The reservationService.cs on the client side:
public async Task<List<ReservationGetDTO>> CreateReservation(ReservationPostDTO reserv) {
var result = await _httpClient.PostAsJsonAsync("api/reservations", reserv);
reservations = await result.Content.ReadFromJsonAsync<List<ReservationGetDTO>>();
return reservations;
}
And the razor page that uses this service:
#code {
[Parameter]
public ShipDTO SelectedShip { get; set; }
private ReservationPostDTO reservation = new ReservationPostDTO {
FromDate = DateTime.Today,
ToDate = DateTime.Today.AddDays(1),
};
async void HandleSubmit() {
reservation.Ship = SelectedShip;
await ReservationService.CreateReservation(reservation);
}
When I press the submit reservation button and the HandleSubmit function is called I got the following error: Microsoft.Data.SqlClient.SqlException (0x80131904): Cannot insert explicit value for identity column in table 'Ships' when IDENTITY_INSERT is set to OFF.
So I tried this:
public async Task CreateReservation(ReservationGetDTO reservationDTO)
{
await _context.Database.ExecuteSqlRawAsync("SET IDENTITY_INSERT [dbo].[Reservations] ON");
_context.Reservations.Add(Mapper.Map(reservationDTO, new Reservation()));
await _context.SaveChangesAsync();
await _context.Database.ExecuteSqlRawAsync("SET IDENTITY_INSERT [dbo].[Reservations] OFF");
}
//I also tried to put this annotation to the Ship
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public Ship Ship { get; set; }
I also tried this modelBuilder.Entity<Reservation>().Property(a => a.Ship).ValueGeneratedNever(); But I got an error that said the ship is a navigation property so i cant use this function
Any ideas?
If I understood you correctly, then ship id property is used as an index by EF by default naming convention, if you set it as code first.
I would try to set this property to a default value of it's kind (0 for int, null for string, etc) - it should allow EF to insert it into the database - this works if you want to actually add a ship.
If there is no ship in newly created entity, than just set ship to null - should do the trick.
THe reason why I did not work was that I tried to add a value to the navigation property on the client side, which you only can do on the server side

How do I add and update items using Entity Framework Core 5 that have many to many relationships?

I've been struggling with this all evening and still don't fully understand how Entity Framework Core works with many to many relationships.
I have a TransportProvider class and a Tag class. It's a many to many relationship. When adding a new TransportProvider you can assign tags. If the tag already exists in the database I'd like to add that existing tag, otherwise I'd like to insert a new tag. This is what I have for my TransportProvider class:
public class TransportProvider
{
public int ID { get; set; }
[Display(Name = "Company name")]
[Required]
[StringLength(200)]
public string CompanyName { get; set; }
... standard properties
public bool Disabled { get; set; }
[NotMapped]
public string SelectedTags { get; set; }
public ICollection<Tag> Tags { get; set; }
}
My tag class:
public class Tag
{
public int ID { get; set; }
[Required]
[StringLength(100)]
public string Name { get; set; }
public ICollection<TransportProvider> TransportProviders { get; set; }
}
And this is my controller function that creates a new transport provider:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("ID,CompanyName,ContactName,ContactTelephone1,ContactTelephone2,ContactEmail,CompanyWebsite,AddressLine1,AddressLine2,Suburb,Province,PostCode,Country,Lat,Lng,SelectedTags,Notes,Disabled")] TransportProvider transportProvider)
{
if (ModelState.IsValid)
{
var selectedTags = !string.IsNullOrEmpty(transportProvider.SelectedTags) ? transportProvider.SelectedTags.Split(',') : new string[0];
_context.TransportProviders.Add(transportProvider);
foreach (var selectedTag in selectedTags)
{
var tag = _context.Tags.SingleOrDefault(t => t.Name.ToLower() == selectedTag);
if (tag == null)
{
tag = new Tag();
tag.Name = selectedTag;
}
transportProvider.Tags.Add(tag);
}
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(transportProvider);
}
and finally my context class:
public class AntelopeContext : DbContext
{
public AntelopeContext(DbContextOptions<AntelopeContext> options) : base(options)
{
}
public DbSet<TransportProvider> TransportProviders { get; set; }
public DbSet<Tag> Tags { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TransportProvider>().ToTable("TransportProvider");
modelBuilder.Entity<Tag>().ToTable("Tag");
}
}
If I try and execute this code I get a NullReferenceException for the line:
transportProvider.Tags.Add(tag);
I don't know why this is so difficult to do. All I want to do is add tags to a transport provider. If the tag is new it needs to insert a new tag record. If not then it just has to link the existing tag.
How do I do this?
Thanks
Many to many relationships require a collection navigation property on both sides. They will be discovered by convention like other types of relationships.
public class TransportProvider
{
public int TransportProviderId { get; set; }
public string CompanyName { get; set; }
public bool Disabled { get; set; }
public ICollection<Tag> Tags { get; set; }
}
public class Tag
{
public int TagId { get; set; }
public string Name { get; set;}
public ICollection<TransportProvider> TransportProviders { get; set; }
}
The way this relationship is implemented in the database is by a join table that contains foreign keys to both TransferProvider and Tag. For example this is what EF will create in a relational database for the above model.
CREATE TABLE [TransportProvider] (
[TransportProviderId] int NOT NULL IDENTITY,
[CompanyName] nvarchar(max) NULL,
[Disable] bit NULL,
CONSTRAINT [PK_TransportProvider] PRIMARY KEY ([TransportProviderId])
);
CREATE TABLE [Tag] (
[TagId] int NOT NULL IDENTITY,
[Name] nvarchar(max) NULL,
CONSTRAINT [PK_Tag] PRIMARY KEY ([TagId])
);
CREATE TABLE [TransportProviderTag] (
[TransportProviderId] int NOT NULL,
[TagId] int NOT NULL,
CONSTRAINT [PK_TransportProviderTag] PRIMARY KEY ([TransportProviderId], [TagId]),
CONSTRAINT [FK_TransportProviderTag_TransportProviders_TransportProviderId] FOREIGN KEY ([TransportProviderId]) REFERENCES [TransferProviders] ([TransferProviderId]) ON DELETE CASCADE,
CONSTRAINT [FK_TransportProviderTag_Tags_TagId] FOREIGN KEY ([TagId]) REFERENCES [Tags] ([TagId]) ON DELETE CASCADE
);
Internally, EF creates an entity type to represent the join table that will be referred to as the join entity type.
This is a code first approach.
You have first to create TransferProvider and Tag, and then add what row with them in TransferProviderTag table
Since you didn't bind the Tags property, it will default be null, you need to initialize the Tags in TransportProvider firstly.
public async Task<IActionResult> Create([Bind("ID,CompanyName,ContactName,ContactTelephone1,ContactTelephone2,ContactEmail,CompanyWebsite,AddressLine1,AddressLine2,Suburb,Province,PostCode,Country,Lat,Lng,SelectedTags,Notes,Disabled")] TransportProvider transportProvider)
{
transportProvider.Tags = new List<Tag>();
//...
}
Finally! I got it working. I'm not sure this is the 'correct' way, but it seems to work.
I was under the impression that EF Core 5 didn't require joining tables in many-to-many relationships. However when I tried to execute without a joining table I was getting an error about a joining table not being present. I therefore added one as suggested.
I then manually created the TransportProvider, manually checked for a Tag and created if it didn't exist, then manually entered the joining table record. I still feel this probably isn't the most efficient way of doing things, but it works. Code in case anyone is interested:
public async Task<IActionResult> Create([Bind("ID,CompanyName,ContactName,ContactTelephone1,ContactTelephone2,ContactEmail,CompanyWebsite,AddressLine1,AddressLine2,Suburb,Province,PostCode,Country,Lat,Lng,SelectedTags,Notes,Disabled")] TransportProvider transportProvider)
{
if (ModelState.IsValid)
{
var selectedTags = !string.IsNullOrEmpty(transportProvider.SelectedTags) ? transportProvider.SelectedTags.Split(',') : new string[0];
transportProvider.TransportProviderTags = new List<TransportProviderTag>();
_context.TransportProviders.Add(transportProvider);
await _context.SaveChangesAsync();
foreach (var selectedTag in selectedTags)
{
var tag = _context.Tags.SingleOrDefault(t => t.Name.ToLower() == selectedTag);
if (tag == null)
{
tag = new Tag();
tag.Name = selectedTag;
_context.Tags.Add(tag);
await _context.SaveChangesAsync();
}
var tpt = new TransportProviderTag();
tpt.TransportProviderID = transportProvider.ID;
tpt.TagID = tag.ID;
transportProvider.TransportProviderTags.Add(tpt);
await _context.SaveChangesAsync();
}
return RedirectToAction(nameof(Index));
}
return View(transportProvider);
}
Updated context class:
public class AntelopeContext : DbContext
{
public AntelopeContext(DbContextOptions<AntelopeContext> options) : base(options)
{
}
public DbSet<TransportProvider> TransportProviders { get; set; }
public DbSet<Tag> Tags { get; set; }
public DbSet<TransportProviderTag> TransportProviderTags { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TransportProvider>().ToTable("TransportProvider");
modelBuilder.Entity<Tag>().ToTable("Tag");
modelBuilder.Entity<TransportProviderTag>().ToTable("TransportProviderTag");
modelBuilder.Entity<TransportProviderTag>()
.HasKey(tpt => new { tpt.TransportProviderID, tpt.TagID });
modelBuilder.Entity<TransportProviderTag>()
.HasOne(tpt => tpt.TransportProvider)
.WithMany(tp => tp.TransportProviderTags)
.HasForeignKey(tpt => tpt.TransportProviderID);
modelBuilder.Entity<TransportProviderTag>()
.HasOne(tpt => tpt.Tag)
.WithMany(t => t.TransportProviderTags)
.HasForeignKey(tpt => tpt.TagID);
}
}
And thanks #MilutinSpaic and #mj1313 for steering me in the right direction. Hopefully this will help someone else

Entity Framework 6 custom many-to-many child with implicit insert and delete

I have parent object (LoanApplication) with a child (LoanApplicationQualificationTypes) that is a custom many-to-many table. The reason I have a custom is that it has two audit columns that need to be populated (ModifiedBy, ModifiedDate).
To get the children that were added or removed from the child collection to be persisted in the database correctly, I had to explicitly handle.
Below is the code (simplified by removing other properties that were germane to the question).
Parent (part of many-to-many):
[Serializable]
[Table("LoanApplication")]
public class LoanApplication : BaseDomainModelWithId, ILoanApplication
{
[Key]
[Column("LoanApplicationId")]
public override int? Id { get; set; }
[ForeignKey("LoanApplicationId")]
public virtual ICollection<LoanApplicationQualificationTypes> LoanApplicationQualificationTypes { get; set; }
IReadOnlyCollection<ILoanApplicationQualificationTypes> ILoanApplication.LoanApplicationQualificationTypes
{
get
{
var loanApplicationQualificationTypes = new List<ILoanApplicationQualificationTypes>();
if (LoanApplicationQualificationTypes == null) return loanApplicationQualificationTypes;
loanApplicationQualificationTypes.AddRange(LoanApplicationQualificationTypes);
return loanApplicationQualificationTypes.AsReadOnly();
}
set
{
foreach (var item in value)
{
LoanApplicationQualificationTypes.Add((LoanApplicationQualificationTypes)item);
}
}
}
public LoanApplication() : base()
{
LoanApplicationQualificationTypes = new List<LoanApplicationQualificationTypes>();
}
}
public interface ILoanApplication : IDomainModel, ILoanApplicationBase, IKeyIntId
{
IReadOnlyCollection<ILoanApplicationQualificationTypes> LoanApplicationQualificationTypes { get; set; }
}
Object part of many-to-many:
[Serializable]
[Table("QualificationType")]
public class QualificationType : IQualificationType
{
[Key]
[Column("QualificationTypeId")]
public override int? Id { get; set; }
[Required]
public string TypeName { get; set; }
[Required]
public bool IsActive { get; set; }
public virtual string ModifiedBy { get; set; }
public virtual DateTimeOffset? ModifiedDate { get; set; }
public QualificationType() : { }
}
Custom Many-to-Many:
[Serializable]
[Table("LoanApplicationQualificationTypes")]
public class LoanApplicationQualificationTypes : ILoanApplicationQualificationTypes
{
[Key]
[Column(Order = 1)]
public int? LoanApplicationId { get; set; }
[ForeignKey("LoanApplicationId")]
public virtual LoanApplication LoanApplication { get; set; }
ILoanApplication ILoanApplicationQualificationTypes.LoanApplication
{
get
{
return this.LoanApplication;
}
set
{
this.LoanApplication = (LoanApplication)value;
}
}
[Required]
[Key]
[Column(Order = 2)]
public int QualificationTypeId { get; set; }
[ForeignKey("QualificationTypeId")]
public virtual QualificationType QualificationType { get; set; }
IQualificationType ILoanApplicationQualificationTypes.QualificationType
{
get
{
return this.QualificationType;
}
set
{
this.QualificationType = (QualificationType)value;
}
}
public virtual string ModifiedBy { get; set; }
public virtual DateTimeOffset? ModifiedDate { get; set; }
public LoanApplicationQualificationTypes() { }
}
Update method in LoanApplication Repository:
public bool Update(ILoanApplication entity)
{
using (var db = new MainContext())
{
entity.ModifiedDate = DateTime.UtcNow;
entity.ModifiedBy = UserOrProcessName;
// Add / Remove LoanApplicationQualificationTypes and populate audit columns
if (entity.LoanApplicationQualificationTypes?.Count > 0)
{
var existingItems = db.LoanApplicationQualificationTypes.Where(q => q.LoanApplicationId == entity.Id.Value).ToList();
var newItems = entity.LoanApplicationQualificationTypes.Where(q => existingItems.All(e => e.QualificationTypeId != q.QualificationTypeId));
var deletedItems = existingItems.Where(q => entity.LoanApplicationQualificationTypes.All(e => e.QualificationTypeId != q.QualificationTypeId));
foreach (var newItem in newItems)
{
newItem.ModifiedBy = UserOrProcessName;
newItem.ModifiedDate = DateTime.UtcNow;
db.LoanApplicationQualificationTypes.Add((LoanApplicationQualificationTypes)newItem);
}
foreach (var deletedItem in deletedItems)
{
db.LoanApplicationQualificationTypes.Remove((LoanApplicationQualificationTypes)deletedItem);
}
// Need to clear to avoid duplicate objects
((LoanApplication)entity).LoanApplicationQualificationTypes.Clear();
}
db.Entry(entity).State = EntityState.Modified;
db.SaveChanges();
}
return true;
}
Is there a way implement the Update without the explicitly handling adds/updates?
The way I understand it, the question is how to apply the (potential) modifications to the link table without explicitly detecting added/removed links. Also I assume the other part of the link must exist.
It's possible with the following sequence of operations:
First load the actual entity from the database into context, including the links:
var dbEntity = db.LoanApplication
.Include(e => e.LoanApplicationQualificationTypes)
.FirstOrDefault(e => e.Id == entity.Id);
This will allow change tracker to determine the correct add/update/delete link operations for you later.
Then apply the primitive master data changes:
db.Entry(dbEntity).CurrentValues.SetValues(entity);
dbEntity.ModifiedDate = DateTime.UtcNow;
dbEntity.ModifiedBy = UserOrProcessName;
Finally, replace the links with the ones from the incoming entity. To avoid navigation property references pointing to different objects (and in particular to prevent EF trying to create the new records for the other side objects of the relation), do not use directly the incoming objects, but create stub objects with only FK properties set:
dbEntity.LoanApplicationQualificationTypes = entity.LoanApplicationQualificationTypes
.Select(e => new LoanApplicationQualificationTypes
{
LoanApplicationId = e.LoanApplicationId,
QualificationTypeId = e.QualificationTypeId,
ModifiedDate = DateTime.UtcNow,
ModifiedBy = UserOrProcessName,
})
.ToList();
And that's it. At this point the change tracker has all the necessary information to produce the correct commands when you call db.SaveChanges().
One thing to mention. If you look at db.ChangeTracker.Entries at this point, you'll probably notice that all the old links are marked as Deleted, all the incoming as Added and there are no Modified entries. Don't worry. EF is smart enough and will convert Deleted + Added pairs with the same PK to single update commands.
The whole method:
public bool Update(ILoanApplication entity)
{
using (var db = new MainContext())
{
var dbEntity = db.LoanApplication
.Include(e => e.LoanApplicationQualificationTypes)
.FirstOrDefault(e => e.Id == entity.Id);
if (dbEntity == null) return false;
db.Entry(dbEntity).CurrentValues.SetValues(entity);
dbEntity.ModifiedDate = DateTime.UtcNow;
dbEntity.ModifiedBy = UserOrProcessName;
dbEntity.LoanApplicationQualificationTypes = entity.LoanApplicationQualificationTypes
.Select(e => new LoanApplicationQualificationTypes
{
LoanApplicationId = e.LoanApplicationId,
QualificationTypeId = e.QualificationTypeId,
ModifiedDate = DateTime.UtcNow,
ModifiedBy = UserOrProcessName,
})
.ToList();
db.SaveChanges();
return true;
}
}

Entity Framework many to many relationship insert

I have a Web API application with entity framework 6.
I have 2 entities - Player and Match with many to many relationship between them:
public class Player {
public string PlayerId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<Match> Matches { get; set; }
}
public class Match {
public string MatchId { get; set; }
public DateTime DateTime { get; set; }
public virtual ICollection<Player> Players { get; set; }
}
This is how my DBContext looks like:
public class FIFA15RankingContext : DbContext {
public DbSet<Player> Players { get; set; }
public DbSet<Match> Matches { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Entity<Player>()
.HasMany(t => t.Matches)
.WithMany(t => t.Players)
.Map(m => {
m.ToTable("PlayerMatches");
m.MapLeftKey("PlayerId");
m.MapRightKey("MatchId");
}
);
base.OnModelCreating(modelBuilder);
}
}
EF created for me behind the scenes the table PlayerMatches (PlayerId, MatchId).
This is my MatchesController (as scaffolded by VS 2013):
// POST: api/Matches
[ResponseType(typeof(Match))]
public async Task<IHttpActionResult> PostMatch(Match match)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
db.Matches.Add(match);
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateException)
{
if (MatchExists(match.MatchId))
return Conflict();
else
throw;
}
return CreatedAtRoute("DefaultApi", new { id = match.MatchId }, match);
}
I want to be able to create new match and associate with it existing users.
I couldn't make it work in any other way but to add this code (which smells pretty bad) before db.Matches.Add(match):
var playersFromDB = new List<Player>();
if (match.Players != null) {
foreach (var p in match.Players) {
playersFromDB.Add(db.Players.Find(p.PlayerId));
}
}
match.Players = playersFromDB;
What am I doing wrong?
If all players already exists in database, you can attach them to dbcontext instead of load from db.
if (match.Players != null) {
foreach (var player in match.Players) {
db.Players.Attach(player);
}
}
Manually attach the User entities and set their state to Unchanged
foreach( var p in match.Players )
{
db.Entry( p ).State = EntityState.Unchanged;
}

Categories

Resources