Creating an Update method in a Repository - attaching/detaching entities not working - asp.net-mvc

I have the following Update method:
public Folder UpdateFolder(Folder folder)
{
_db.Folders.Attach(folder); // error happens here
var entry = _db.Entry(folder);
entry.Property(e => e.Title).IsModified = true;
SaveChanges();
return entry.Entity;
}
I get "An object with the same key already exists" when I try to Attach. If I remove that line, I get "The entity of type "folder" does not exist in this context".
I then try the following:
public Folder UpdateFolder(Folder folder)
{
var entry = _db.Entry(folder);
entry.State= EntityState.Detached;
_db.Folders.Attach(folder);
entry.Property(e => e.Title).IsModified = true;
And I get the same error when I hit the call to Attach.
Here's where I'm calling it from (test method):
// Update Folder, Check Folder
homeFolder.Title = "Updated";
_dtoServices.UpdateFolder(homeFolder); // HERE
Assert.AreEqual(_dtoServices.GetFolder(homeFolder.FolderId).Details, "Updated");
In my DtoServices:
public FolderDto UpdateFolder(FolderDto folderDto)
{
var test = _repository.UpdateFolder(folderDto.ToEntity());
return null;
}
In my FolderDto:
public class FolderDto
{
public FolderDto()
{
}
public FolderDto(Folder folder)
{
FolderId = folder.FolderId;
Title = folder.Title;
}
[Key]
public int FolderId { get; set; }
[Required]
public string Title { get; set; }
public Folder ToEntity()
{
var folder = new Folder
{
FolderId = FolderId,
Title = Title,
};
return folder;
}
}
Any idea why this happening? How can I build a more robust check for my update method? I've searched around but can't find anything conclusive.

You have to make sure the entity isn't already attached. I do something like this (TEntity : class):
...
//See if entity is attached already and return if so.
TEntity attached = GetAttached(entityToUpdate);
if(attached == null)
{
//Entity is not attached, attach it.
}
...
//Generic helper to check for attached entity
private TEntity GetAttached(TEntity aEntity)
{
var lObjectContext = ((IObjectContextAdapter)context).ObjectContext;
EntityKey lKey = GetEntityKey(aEntity);
ObjectStateEntry entry;
if (!lObjectContext.ObjectStateManager.TryGetObjectStateEntry(lKey, out entry)) return null;
if (entry.State == EntityState.Detached) return null;
return (TEntity)entry.Entity;
}

Related

ApplicationUser is not saving changes to field after there is already a value in that field

I'm working on a project that keeps track of the hours employees have worked between different crews. The way the app is supposed to work is that an employee submits a Daysheet form that has their clock-in and clock-out time and the id of their Field Manager(FMId). Each Field Manager has a column in the database that should keep track of the DaysheetIds of forms that are submitted with their FMId. The problem is that this column is only saving the first DaysheetId that gets submitted. Once there's a value in that column, the Field Manager doesn't update to add any other DaysheetIds.
To keep my post concise, I'm trying to post only the relevant code, but let me know if I'm missing something that would be important.
Here's the action in my controller where the Field Manager should be updated. I added the var FM just before the return to see what was happening to the Field Manager with the debugger.
public IActionResult Submit(EmployeeDaySheetViewModel employeeDaySheetViewModel)
{
if (ModelState.IsValid)
{
var newDaySheet = new EmployeeDaySheet
{
Id = employeeDaySheetViewModel.DaysheetId,
EmployeeId = employeeDaySheetViewModel.EmployeeId,
EmployeeName = employeeDaySheetViewModel.EmployeeName,
FMId = employeeDaySheetViewModel.FMId,
Date = employeeDaySheetViewModel.Date,
ClockIn = employeeDaySheetViewModel.ClockIn,
ClockOut = employeeDaySheetViewModel.ClockOut,
};
var success = _employeeDaySheetRepository.AddDaySheet(newDaySheet);
if (success)
{
_applicationUserRepository
.AddCrewDaySheetToFieldManager(employeeDaySheetViewModel.FMId,
employeeDaySheetViewModel.DaysheetId);
TempData["UserMessage"] = "Successfully submitted daysheet for " + DateTime.Now.Day.ToString();
}
else
{
TempData["ErrorMessage"] = "Unable to submit daysheet. Please try again in a few minutes.";
}
}
var FM = _applicationUserRepository.GetFieldManagerById(employeeDaySheetViewModel.FMId);
return Redirect("/home/");
Here is the AddCrewDaySheetToFieldManager method in my user repository that's being called in the controller action:
public bool AddCrewDaySheetToFieldManager(string FMId, string DaySheetId)
{
var fieldManager = _applicationDbContext.ApplicationUsers
.FirstOrDefault(u => u.Id == FMId);
if (fieldManager.CrewDaySheetIds != null)
{
var oldCrewIds = fieldManager.CrewDaySheetIds;
// If the user deletes all their crewIds, the database leaves an empty string in their crewIds column.
// Replacing user.crewIds that has "" at index 0 is the same as starting a new list.
if (oldCrewIds[0] == "")
{
var newCrewIds = new List<string> { DaySheetId };
fieldManager.CrewDaySheetIds = newCrewIds;
}
else
{
fieldManager.CrewDaySheetIds.Add(DaySheetId);
}
}
else { fieldManager.CrewDaySheetIds = new List<string> { DaySheetId }; }
_applicationDbContext.SaveChanges();
return true;
This is what my ApplicationUser looks like:
public class ApplicationUser : IdentityUser
{
public string Tier { get; set; }
public bool IsFieldManager { get; set; }
public double HourRate { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<string> PayrollObjectIds { get; set; }
public List<string> CrewDaySheetIds { get; set; }
}
In order to convert the lists attributes of this object to strings, I've got this in my DbContext
protected override void OnModelCreating(ModelBuilder builder)
{
var splitStringConverter = new ValueConverter<List<string>, string>(v => string.Join(";", v), v => v.Split(new[] { ';' }).ToList());
builder.Entity<ApplicationUser>().Property(nameof(ApplicationUser.PayrollObjectIds)).HasConversion(splitStringConverter);
builder.Entity<ApplicationUser>().Property(nameof(ApplicationUser.CrewDaySheetIds)).HasConversion(splitStringConverter);
base.OnModelCreating(builder);
//I also seed some data here//
}
What I can't figure out is the point at which the data is not getting saved. When I run the debugger, I can see the fieldManager is getting updated. In the AddCrewDaySheetToFieldManager method, fieldManager.CrewDaySheetIds has the DaysheetId. Then, back in the action, just before return Redirect('/home/')the FM.CrewDaySheetIds still has the DaysheetId. However, when I look at the database or try to access the CrewDaysheetIds on the FM user, only the first DaysheetId is there.
I suspect that there's something going wrong with the splitStringConversion, but I've used the same code in another project and not had this issue, so I'm stuck for what to do.
I didn't understand your question correctly but if you want update or insert some data in your database you can use _applicationDbContext.ApplicationUsers.add(fieldManager); for insert, and _applicationDbContext.Entry(fieldManager).State = EntityState.Modeified; for update. but you didn't use any of them before _applicationDbContext.SaveChanges().
I think you have to write this:
public bool AddCrewDaySheetToFieldManager(string FMId, string DaySheetId)
{
var fieldManager = _applicationDbContext.ApplicationUsers
.FirstOrDefault(u => u.Id == FMId);
if (fieldManager.CrewDaySheetIds != null)
{
var oldCrewIds = fieldManager.CrewDaySheetIds;
// If the user deletes all their crewIds, the database leaves an empty string in their crewIds column.
// Replacing user.crewIds that has "" at index 0 is the same as starting a new list.
if (oldCrewIds[0] == "")
{
var newCrewIds = new List<string> { DaySheetId };
fieldManager.CrewDaySheetIds = newCrewIds;
}
else
{
fieldManager.CrewDaySheetIds.Add(DaySheetId);
}
}
else { fieldManager.CrewDaySheetIds = new List<string> { DaySheetId }; }
_applicationDbContext.entry(fieldManager).State = EntityState.Modified;
_applicationDbContext.SaveChanges();
return true;

How to upload image file in MVC?

I have created a folder called Images under my wwwroot, how do I upload and save an image file to that folder in MVC? My Controller has an error, please help correct it.
[Authorize]
public IActionResult Add()
{
return View();
}
[Authorize]
[HttpPost]
public IActionResult Add(TheMobileSuit themobilesuits, IFormFile photo)
{
DbSet<TheMobileSuit> dbs = _dbContext.TheMobileSuit;
dbs.Add(themobilesuits);
themobilesuits.PicFile = Path.GetFileName(photo.FileName);
string fname = "Images/" + themobilesuits.PicFile;
UploadFile(photo, fname);
if (_dbContext.SaveChanges() == 1)
TempData["Msg"] = "New Order Added!";
else
TempData["Msg"] = "Error.";
return RedirectToAction("Index");
}
private bool UploadFile(IFormFile ufile, string fname)
{
if (ufile.Length > 0)
{
string fullpath = Path.Combine(_env.WebRootPath, fname);
using (var fileStream =
new FileStream(fullpath, FileMode.Create))
{
ufile.CopyToAsync(fileStream);
}
return true;
}
return false;
}
I think you are close with this. When I copied your code and filled in the details with what I thought they would probably be I got 2 exceptions.
The first was a directory not found exception. This was because the images directory did not exist. Check yours exists and if it doesn't create it.
The second was that TheMobileSuit was an invalid object name. This is to do with your dbcontext. Looking at your controller I would guess your dbcontext looks something like this
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options)
{
}
public DbSet<TheMobileSuit> TheMobileSuit { get; set; }
}
For me the code worked once I changed it to this
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options)
{
}
public DbSet<TheMobileSuit> TheMobileSuits { get; set; } //note the s at the end
}
My understanding is that most people will give these dbset properties a plural name and I think that once this is followed your code will work fine.

BreezeSharp Attach Property key not found

I'm implementing an application with Breezesharp. I ran into a issue when insert the entity in the EntityManager. The error is:
There are no KeyProperties yet defined on EntityType: 'TransportReceipt:#Business.DomainModels'
I already faced this error with my first entity type "Customer" and implement a mismatching approach as suggested here. In that case I made the get operation against my WebApi with success. But now I'm creating the TransportReceipt entity inside my application.
Mapping mismatch fix
public static class ExtendMap
{
private static bool? executed;
public static void Execute(MetadataStore metadataStore) {
if (ExtendMap.executed == true)
{
return;
}
var customerBuilder = new EntityTypeBuilder<Customer>(metadataStore);
customerBuilder.DataProperty(t => t.id).IsPartOfKey().IsAutoIncrementing();
var transportReceiptBuilder = new EntityTypeBuilder<TransportReceipt>(metadataStore);
transportReceiptBuilder.DataProperty(t => t.id).IsPartOfKey().IsAutoIncrementing();
var transportReceiptAttachmentBuilder = new EntityTypeBuilder<TransportReceiptAttachment>(metadataStore);
transportReceiptAttachmentBuilder.DataProperty(t => t.id).IsPartOfKey().IsAutoIncrementing();
var uploadedFileBuilder = new EntityTypeBuilder<UploadedFile>(metadataStore);
uploadedFileBuilder.DataProperty(t => t.id).IsPartOfKey().IsAutoIncrementing();
ExtendMap.executed = true;
}
}
My base dataservice core code
public abstract class SimpleBaseDataService
{
public static string Metadata { get; protected set; }
public static MetadataStore MetadataStore { get; protected set; }
public string EntityName { get; protected set; }
public string EntityResourceName { get; protected set; }
public EntityManager EntityManager { get; set; }
public string DefaultTargetMethod { get; protected set; }
static SimpleBaseDataService()
{
try
{
var metadata = GetMetadata();
metadata.Wait();
Metadata = metadata.Result;
MetadataStore = BuildMetadataStore();
}
catch (Exception ex)
{
var b = 0;
}
}
public SimpleBaseDataService(Type entityType, string resourceName, string targetMethod = null)
{
var modelType = typeof(Customer);
Configuration.Instance.ProbeAssemblies(ConstantsFactory.BusinessAssembly);
try
{
this.EntityName = entityType.FullName;
this.EntityResourceName = resourceName;
this.DefaultTargetMethod = (string.IsNullOrWhiteSpace(targetMethod) ? "GetAllMobile" : targetMethod);
var dataService = new DataService($"{ConstantsFactory.Get.BreezeHostUrl}{this.EntityResourceName}", new CustomHttpClient());
dataService.HasServerMetadata = false;
this.EntityManager = new EntityManager(dataService, SimpleBaseDataService.MetadataStore);
this.EntityManager.MetadataStore.AllowedMetadataMismatchTypes = MetadataMismatchTypes.AllAllowable;
// Attach an anonymous handler to the MetadataMismatch event
this.EntityManager.MetadataStore.MetadataMismatch += (s, e) =>
{
// Log the mismatch
var message = string.Format("{0} : Type = {1}, Property = {2}, Allow = {3}",
e.MetadataMismatchType, e.StructuralTypeName, e.PropertyName, e.Allow);
// Disallow missing navigation properties on the TodoItem entity type
if (e.MetadataMismatchType == MetadataMismatchTypes.MissingCLRNavigationProperty &&
e.StructuralTypeName.StartsWith("TodoItem"))
{
e.Allow = false;
}
};
}
catch (Exception ex)
{
var b = 0;
}
}
}
This is who I'm trying to add the new entity
//DataService snippet
public void AttachEntity(T entity)
{
this.EntityManager.AttachEntity(entity, EntityState.Added);
}
//Business
this.TransportReceipt = new TransportReceipt { id = Guid.NewGuid(), date = DateTime.Now, customerId = Customer.id/*, customer = this.Customer*/ };
this.Attachments = new List<TransportReceiptAttachment>();
this.TransportReceipt.attachments = this.Attachments;
TransportReceiptDataService.AttachEntity(this.TransportReceipt);
When I try to add add the entity to the EntityManager, I can see the custom mapping for all my entity classes.
So my question is what I'm doing wrong.
Ok. That was weird.
I changed the mapping for a new fake int property and works. I'll test the entire save flow soon and I'll share the result here.
Update
I moved on and start removing Breezesharp. The Breezesharp project is no up-to-date and doesn't have good integration with Xamarin. I'll appreciate any comment with your experience.

Track Changes in navigation property

public partial class ClaimEntities : DbContext
{
public ClaimEntities()
: base("name=ClaimEntities")
{
this.Configuration.LazyLoadingEnabled = false;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<ClaimInformation> ClaimInformations { get; set; }
public DbSet<ClaimInformation_HealthCareCode> ClaimInformation_HealthCareCode { get; set; }
}
}
public partial class ClaimInformation
{
public List<ClaimInformation_HealthCareCode> OtherDiagnosisCodes
{
get
{
return this.ClaimInformation_HealthCareCode.Where(c => c.CodeQualifier == "BF" || c.CodeQualifier == "ABF").ToList();
}
}
}
public partial class ClaimInformation
{
public ClaimInformation()
{
this.ClaimInformation_HealthCareCode = new ObservableListSource<ClaimInformation_HealthCareCode>();
}
public virtual ObservableListSource<ClaimInformation_HealthCareCode> ClaimInformation_HealthCareCode { get; set; }
}
ClaimInformation_HealthcareCodes is a navigation property on the entity ClaimInformation. 1-M between ClaimInformation and ClaimInformation_healthcareCodes. One Claim can have many ClaimHealthcareCodes.
This is how it is loaded in the context
_context.ClaimInformations.Include(h => h.ClaimInformation_HealthCareCode)
How do I make the context detect a change in the navigation property. In the code I am deleting a healthcareCode entry. This is a list in the ClaimInformationClass and is virtual. 1-M
this.claiminformation.claiminfo_healthcarecodes[i].remove(); This line is connected to the context.
private string GetTableName(DbEntityEntry dbEntry)
{
string entryName = dbEntry.Entity.GetType().Name;
int length = entryName.IndexOf('_');
TableAttribute tableAttr = dbEntry.Entity.GetType().GetCustomAttributes(typeof(TableAttribute),
false).SingleOrDefault() as TableAttribute;
string tableName = tableAttr != null ? tableAttr.Name : entryName.Substring(0,length);
return tableName;
}
This code returns the changed entry as 'ClaimInformation' which is partly true but it has to go a little deeper to to the navigation property which has a deleted entry.
private string GetTableName(DbEntityEntry ent)
{
ObjectContext objectContext = ((IObjectContextAdapter)this.context).ObjectContext;
System.Type entityType = ent.Entity.GetType();
if (entityType.BaseType != null && entityType.Namespace == "System.Data.Entity.DynamicProxies")
entityType = entityType.BaseType;
string entityTypeName = entityType.Name;
EntityContainer container =
objectContext.MetadataWorkspace.GetEntityContainer(objectContext.DefaultContainerName, DataSpace.CSpace);
string entitySetName = (from meta in container.BaseEntitySets
where meta.ElementType.Name == entityTypeName
select meta.Name).First();
return entitySetName;
}
Changed it to get the entity name instead of table name

Entity framework savechanges error

I have a wizard step in which a user fills in fields. I then use json to save the values into my database for each wizard step.
However, in my repository I have my savechanges(). But it wont save the changes, instead it throws an error:
Entities in 'NKImodeledmxContainer.SelectedQuestion' participate in the 'QuestionSelectedQuestion' relationship. 0 related 'Question' were found. 1 'Question' is expected.
Anyone know how to get rid of the error? Do I have to get the ID from Question and save it aswell to my database or can I change something in EF so the error message is not getting thrown?
This is my post in my controller:
[HttpPost]
public JsonResult AnswerForm(int id, SelectedQuestionViewModel model)
{
bool result = false;
var goalCardQuestionAnswer = new GoalCardQuestionAnswer();
goalCardQuestionAnswer.SelectedQuestion = new SelectedQuestion();
goalCardQuestionAnswer.SelectedQuestion.Id = model.QuestionID;
goalCardQuestionAnswer.Comment = model.Comment;
goalCardQuestionAnswer.Grade = model.Grade;
if (goalCardQuestionAnswer.Grade != null)
{
answerNKIRepository.SaveQuestionAnswer(goalCardQuestionAnswer);
answerNKIRepository.Save();
result = true;
return Json(result);
}
answerNKIRepository.SaveQuestionAnswer(goalCardQuestionAnswer);
answerNKIRepository.Save();
return Json(result);
}
My Repository
public class AnswerNKIRepository
{
private readonly NKImodeledmxContainer db = new NKImodeledmxContainer();
public List<SelectedQuestion> GetAllSelectedQuestionsByGoalCardId(int goalCardId)
{
return db.SelectedQuestion.Where(question => question.GoalCard.Id == goalCardId).ToList();
}
public void SaveQuestionAnswer(GoalCardQuestionAnswer goalCardQuestionAnswer)
{
db.GoalCardQuestionAnswer.AddObject(goalCardQuestionAnswer);
}
public void Save()
{
db.SaveChanges();
}
}
This is my ViewModel:
public class SelectedQuestionViewModel
{
public int? Grade { get; set; }
public string Comment { get; set; }
public string SelectedQuestionText { get; set; }
public int QuestionID { get; set; }
}
This is my database model:
The exception complains that SelectedQuestion.Question is a required navigation property but you don't set this property in your code. Try to load the question by Id from the repository and set it to the SelectedQuestion.Question reference: Replace this line ...
goalCardQuestionAnswer.SelectedQuestion.Id = model.QuestionID;
...by...
goalCardQuestionAnswer.SelectedQuestion.Question =
answerNKIRepository.GetQuestionById(model.QuestionID);
And in your repository add the method:
public Question GetQuestionById(int id)
{
return db.Question.Single(q => q.Id == id);
}

Resources