.Update() returns "Cannot update identity column" - entity-framework

There is a link on an Index-type display that directs control to the method below. The Voucher Status is updated to "Reconciled" then a save is attempted.
Everything appears to execute as it should until the Update/Save when I get the exception, "Cannot update identity column."
There are no navigation properties in the Voucher model. Both Identity columns are populated nicely on insert.
The model is here:
public class Voucher
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string VoucherId { get; set; }
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Int64 VoucherNumber { get; set; }
[Required]
public string StudentId { get; set; }
public string FullName { get; set; }
[Required]
public string VoucherType { get; set; }
[Required]
public DateTime VoucherCreateDate { get; set; }
[Required]
public string VoucherStatus { get; set; }
}
public ViewResult ReconcileVoucher(string id) //, Voucher voucher)
{
Voucher voucher = _context.Vouchers
.Single(m => m.VoucherId == id);
if (id != voucher.VoucherId)
{
ModelState.AddModelError("InputStatus", "Indicated Voucher is not present " + voucher.FullName);
return View();
}
voucher.VoucherStatus = "Reconciled";
if (ModelState.IsValid)
{
try
{
_context.Update(voucher);
_context.SaveChanges();
ModelState.AddModelError("InputStatus", "Voucher was Reconciled for " + voucher.FullName);
}
catch (DbUpdateConcurrencyException ex)
{
CreateLogRow("Reconcile Voucher", "testUser", "Concurrency exception for " + voucher.StudentId, ex.Message);
if (!VoucherExists(voucher.VoucherId))
{
ModelState.AddModelError("InputStatus", "The Voucher for " + voucher.FullName + " cannot be found");
return View();
}
else
{
throw;
}
}
CreateLogRow("Reconcile Voucher", "testUser", "Voucher was edited for " + voucher.StudentId + " - " + voucher.FullName, null);
return View();
}
return View(voucher);
}

When you fetch an entity from the DbContext within the call and update property, you should not call Update, but rather just call SaveChanges.
I.e.
Voucher voucher = _context.Vouchers
.Single(m => m.VoucherId == id);
// ...
voucher.VoucherStatus = "Reconciled";
// ...
_context.SaveChanges();
Update will generate a statement that will update all columns on an entity, and while that should exclude columns that are marked as DatabaseGeneratedOption.Identity this may not be the case here. By letting the change tracking do its thing without the Update call, EF will generate an UPDATE statement for just the column(s) that changed.
Update would be more applicable when accepting an entity (as you had commented out) and attaching/updating its state as a whole. I don't recommend this approach as it overwrites all values and is vulnerable to a number of issues including stale data updates plus unexpected tampering from the client. (Modifying columns your UI does not allow by man-in-the-browser/middle attacks) It is better to load the entity fresh, validate incoming data, check row versions/modified timestamps for stale updates, etc. rather than accepting an entity at face value and pushing it onto the DB.
Edit: If these IDs are GUIDs in the DB, then why not cast them as GUIDs in the entity?

You're marking all the properties as modified by calling
_context.Update(voucher);
Either omit this call and let the change tracker determine the updated columns, or explicitly mark the VoucherNumber property as unmodified.
db.Update(voucher);
db.Entry(voucher).Property(nameof(Voucher.VoucherNumber)).IsModified = false;

Related

Entity Framework's FromSql method to execute a SQL Server table-valued function that uses an inner join select statement

Output screenshot
I created the entity class to match the function columns, included the DbSet in the context class, and completed the method in the repository, but the function call using FromSql is not working.
Here's the code:
SQL Server function:
CREATE FUNCTION dbo.ufn_viewInvoices()
RETURN TABLE
AS
RETURN
(SELECT
i.InvoiceId, i.CustomerId, c.CompanyName,
c.LastName, a.StreetNumber, a.StreetName,
a.City, i.Revenue, i.DateStarted, i.DateCompleted,
i.DaysEstimate, i.DaysActual
FROM
Invoices i, Customers c, Addresses a
WHERE
i.CustomerId = c.CustomerId
AND i.WorkAddressId = a.AddressId);
My entity class to match the columns in the function's select statement:
public class InvoiceCustomerNameAddresses
{
[Key]
public byte InvoiceId { get; set; }
public byte CustomerId { get; set; }
public string CompanyName { get; set; }
public string LastName { get; set; }
public string StreetNumber { get; set; }
public string StretName { get; set; }
public string City { get; set; }
public decimal? Revenue { get; set; }
public DateTime? DateStarted { get; set; }
public DateTime? DateCompleted { get; set; }
public byte? DaysEstimate { get; set; }
public byte? DaysActual { get; set; }
}
The DbSet in the DbContext class:
public DbSet<InvoiceCustomerNameAddresses> InvoiceCustomerNameAddresses { get; set; }
This is the method in the repository:
public List<InvoiceCustomerNameAddresses> GetInvoicesUsingTVF()
{
List<InvoiceCustomerNameAddresses> invoicesList;
try
{
Console.WriteLine("Inside GetInvoicesUsingTVF method in repo, about to call function");
invoicesList = context.InvoiceCustomerNameAddresses.FromSql("SELECT * FROM
[dbo].ufn_viewInvoices()").ToList();
Console.WriteLine("Function called, invoicesList count: " + invoicesList.Count);
}
catch (Exception)
{
Console.WriteLine("In repo's catch, so something went wrong");
invoicesList = null;
Console.WriteLine("An error occurred");
}
return invoicesList;
}
Method in controller:
[HttpGet]
public List<InvoiceCustomerNameAddresses> GetAllInvoicesUsingTVF()
{
List<InvoiceCustomerNameAddresses> detailedInvoicesList = null;
try
{
Console.WriteLine("Inside controller, about to call the repo's getInvoicesUsingTVF method");
detailedInvoicesList = invoiceRepository.GetInvoicesUsingTVF();
Console.WriteLine("Received detailedInvoicesList, count: " + detailedInvoicesList.Count);
}
catch (Exception)
{
Console.WriteLine("In catch block, so something went wrong");
detailedInvoicesList = null;
}
return detailedInvoicesList;
}
Please note:
Console printout statements in the code above, before and after the function call with .FromSql method: the first console printout got executed, but there's an obvious problem with the function call statement execution because the next console printout that gets executed is the one in the catch block.
Note in the output shown in the screenshot the line that says "Executing ObjectResult, writing value of type 'null'". This reflects the statement in the catch block: "invoicesLIst = null" (see picture attached).
Note that this function call does not use parameters, and lastly, executing this function from SQL Server Management Studio works perfectly fine.
Question: what am I missing that my function call with the .FromSql is not working?
I posted this question because after a while, the obvious does not seem so obvious and an extra pair of eyes could maybe help in seeing what I was missing. I was not getting an exception but was not getting the data I expected (screenshot of output attached) because I had a misspelling mistake. The properties in the entity class MUST match the column names of the select statement in the function being called with the EF's FromSql method. Therefore, 'StretName' in the code above for the entity is not recognized; it should be 'StreetName' instead. It works perfectly fine now.

EF6:How to include subproperty with Select so that single instance is created. Avoid "same primary key" error

I'm trying to fetch (in disconnected way) an entity with its all related entities and then trying to update the entity. But I'm getting the following error:
Attaching an entity of type 'Feature' failed because another entity of the same type already has the same primary key value.
public class Person
{
public int PersonId { get; set; }
public string Personname { get; set }
public ICollection Addresses { get; set; }
}
public class Address
{
public int AddressId { get; set; }
public int PersonId { get; set; }
public string Line1 { get; set; }
public string City { get; set; }
public string State { get; set; }
public Person Person { get; set; }
public ICollection<Feature> Features { get; set; }
}
// Many to Many: Represented in database as AddressFeature (e.g Air Conditioning, Central Heating; User could select multiple features of a single address)
public class Feature
{
public int FeatureId { get; set; }
public string Featurename { get; set; }
public ICollection<Address> Addresses { get; set; } // Many-To-Many with Addresses
}
public Person GetCandidate(int id)
{
using (MyDbContext dbContext = new MyDbContext())
{
var person = dbContext.People.AsNoTracking().Where(x => x.PersonId == id);
person = person.Include(prop => prop.Addresses.Select(x => x.Country)).Include(prop => prop.Addresses.Select(x => x.Features));
return person.FirstOrDefault();
}
}
public void UpdateCandidate(Person newPerson)
{
Person existingPerson = GetPerson(person.Id); // Loading the existing candidate from database with ASNOTRACKING
dbContext.People.Attach(existingPerson); // This line is giving error
.....
.....
.....
}
Error:
Additional information: Attaching an entity of type 'Feature' failed because another entity of the same type already has the same primary key value.
It seems like (I may be wrong) GetCandidate is assigning every Feature within Person.Addresses a new instance. So, how could I modify the GetCandidate to make sure that the same instance (for same values) is bing assisgned to Person.Addresses --> Features.
Kindly suggest.
It seems like (I may be wrong) GetCandidate is assigning every Feature within Person.Addresses a new instance. So, how could I modify the GetCandidate to make sure that the same instance (for same values) is bing assisgned to Person.Addresses --> Features.
Since you are using a short lived DbContext for retrieving the data, all you need is to remove AsNoTracking(), thus allowing EF to use the context cache and consolidate the Feature entities. EF tracking serves different purposes. One is to allow consolidating the entity instances with the same PK which you are interested in this case, and the second is to detect the modifications in case you modify the entities and call SaveChanges(), which apparently you are not interested when using the context simply to retrieve the data. When you disable the tracking for a query, EF cannot use the cache, thus generates separate object instances.
What you really not want is to let EF create proxies which hold reference to the context used to obtain them and will cause issues when trying to attach to another context. I don't see virtual navigation properties in your models, so most likely EF will not create proxies, but in order to be absolutely sure, I would turn ProxyCreationEnabled off:
public Person GetCandidate(int id)
{
using (MyDbContext dbContext = new MyDbContext())
{
dbContext.Configuration.ProxyCreationEnabled = false;
var person = dbContext.People.Where(x => x.PersonId == id);
person = person.Include(prop => prop.Addresses.Select(x => x.Country)).Include(prop => prop.Addresses.Select(x => x.Features));
return person.FirstOrDefault();
}
}

DbUpdateException was unhandle by user code

I have such code in controller:
public string UpdateDapAnColumnSelected(int bode, int cauhoi, int selected, int userId)
{
MessageBox.Show(bode + " " + cauhoi + ", dap an: " + selected + "user Id: " + userId);
//add to database THi
THI updateThi = new THI();
updateThi.MABODE = bode;
updateThi.MACAUHOI = cauhoi;
updateThi.MADAPAN = selected;
updateThi.USERID = userId;
if (ModelState.IsValid)
{
db.THIs.Add(updateThi);
db.SaveChanges();
return "a";
}
return "a";
}
In the model context I have:
public virtual DbSet<THI> THIs { get; set; }
Class THI in Model:
public partial class THI
{
public int USERID { get; set; }
public int MABODE { get; set; }
public int MACAUHOI { get; set; }
public int MADAPAN { get; set; }
}
I can't save updateThi to database. Can you help me?
Thank you very much!
You published only partial class of THI, so it's difficult to deduce is Id of THI class unique.
This exception is often caused by attemption to add duplicated primary key. You should have Id or THIID property to follow EF conventions. Thanks to that PK will be configured as an identity column.
Let me know does it work.
===EDIT===
And what is more - you're using Add method to update entity. You shouldn't, because then Id for sure will not be unique. EF will try to add the same record with identical Id into the database once again. Simply update properties and call SaveChanges, without Add() method.

EF code first MVC4 - ArgumentNullException on edit - what am I doing wrong here?

Let's say I have 3 models:
[Table("UserProfile")]
public class UserProfile //this is a standard class from MVC4 Internet template
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
public int UserProfileId { get; set; }
[ForeignKey("UserProfileId")]
public virtual UserProfile UserProfile { get; set; }
}
Now, I'm trying to edit Post
[HttpPost]
public ActionResult Edit(Post post)
{
post.UserProfileId = context.UserProfile.Where(p => p.UserName == User.Identity.Name).Select(p => p.UserId).FirstOrDefault();
//I have to populate post.Category manually
//post.Category = context.Category.Where(p => p.Id == post.CategoryId).Select(p => p).FirstOrDefault();
if (ModelState.IsValid)
{
context.Entry(post.Category).State = EntityState.Modified; //Exception
context.Entry(post.UserProfile).State = EntityState.Modified;
context.Entry(post).State = EntityState.Modified;
context.SaveChanges();
return RedirectToAction("Index");
}
return View(post);
}
And I'm getting ArgumentNullException.
Quick look into debug and I can tell that my Category is null, although CategoryId is set to proper value.
That commented out, nasty-looking trick solves this problem, but I suppose it shouldn't be there at all. So the question is how to solve it properly.
I would say it's something with EF lazy-loading, beacuse I have very similar code for adding Post and in debug there is same scenerio: proper CategoryId, Category is null and despite of that EF automagically resolves that Post <-> Category dependency, I don't have to use any additional tricks.
On edit method, EF has some problem with it, but I cannot figure out what I'm doing wrong.
This is working as intended. Your Post object is not attached to the Context, so it has no reason to do any lazy loading. Is this the full code? I don't understand why you need to set Category as Modified since you're not actually changing anything about it.
Anyway, I recommend you query for the existing post from the Database and assign the relevant fields you want to let the user modify, like such:
[HttpPost]
public ActionResult Edit(Post post)
{
var existingPost = context.Posts
.Where(p => p.Id == post.Id)
.SingleOrDetault();
if (existingPost == null)
throw new HttpException(); // Or whatever you wanna do, since the user send you a bad post ID
if (ModelState.IsValid)
{
// Now assign the values the user is allowed to change
existingPost.SomeProperty = post.SomeProperty;
context.SaveChanges();
return RedirectToAction("Index");
}
return View(post);
}
This way you also make sure that the post the user is trying to edit actually exists. Just because you received some parameters to your Action, doesn't mean they're valid or that the post's Id is real. For example, some ill intended user could decide to edit posts he's not allowed to edit. You need to check for this sort of thing.
UPDATE
On a side note, you can also avoid manually querying for the current user's Id. If you're using Simple Membership you can get the current user's id with WebSecurity.CurrentUserId.
If you're using Forms Authentication you can do Membership.GetUser().ProviderUserKey.

Improve navigation property names when reverse engineering a database

I'm using Entity Framework 5 with Visual Studio with Entity Framework Power Tools Beta 2 to reverse engineer moderately sized databases (~100 tables).
Unfortunately, the navigation properties do not have meaningful names. For example, if there are two tables:
CREATE TABLE Contacts (
ContactID INT IDENTITY (1, 1) NOT NULL,
...
CONSTRAINT PK_Contacts PRIMARY KEY CLUSTERED (ContactID ASC)
}
CREATE TABLE Projects (
ProjectID INT IDENTITY (1, 1) NOT NULL,
TechnicalContactID INT NOT NULL,
SalesContactID INT NOT NULL,
...
CONSTRAINT PK_Projects PRIMARY KEY CLUSTERED (ProjectID ASC),
CONSTRAINT FK_Projects_TechnicalContact FOREIGN KEY (TechnicalContactID)
REFERENCES Contacts (ContactID),
CONSTRAINT FK_Projects_SalesContact FOREIGN KEY (SalesContactID)
REFERENCES Contacts (ContactID),
...
}
This will generate classes like this:
public class Contact
{
public Contact()
{
this.Projects = new List<Project>();
this.Projects1 = new List<Project>();
}
public int ContactID { get; set; }
// ...
public virtual ICollection<Project> Projects { get; set; }
public virtual ICollection<Project> Projects1 { get; set; }
}
public class Project
{
public Project()
{
}
public int ProjectID { get; set; }
public int TechnicalContactID { get; set; }
public int SalesContactID { get; set; }
// ...
public virtual Contact Contact { get; set; }
public virtual Contact Contact1 { get; set; }
}
I see several variants which would all be better than this:
Use the name of the foreign key: For example, everything after the last underscore (FK_Projects_TechnicalContact --> TechnicalContact). Though this probably would be the solution with the most control, this may be more difficult to integrate with the existing templates.
Use the property name corresponding to the foreign key column: Strip off the suffix ID (TechnicalContactID --> TechnicalContact)
Use the concatenation of property name and the existing solution: Example TechnicalContactIDProjects (collection) and TechnicalContactIDContact
Luckily, it is possible to modify the templates by including them in the project.
The modifications would have to be made to Entity.tt and Mapping.tt. I find it difficult due to the lack of intellisense and debug possibilities to make those changes.
Concatenating property names (third in above list) is probably the easiest solution to implement.
How to change the creation of navigational properties in Entity.tt and Mapping.tt to achieve the following result:
public class Contact
{
public Contact()
{
this.TechnicalContactIDProjects = new List<Project>();
this.SalesContactIDProjects = new List<Project>();
}
public int ContactID { get; set; }
// ...
public virtual ICollection<Project> TechnicalContactIDProjects { get; set; }
public virtual ICollection<Project> SalesContactIDProjects { get; set; }
}
public class Project
{
public Project()
{
}
public int ProjectID { get; set; }
public int TechnicalContactID { get; set; }
public int SalesContactID { get; set; }
// ...
public virtual Contact TechnicalContactIDContact { get; set; }
public virtual Contact SalesContactIDContact { get; set; }
}
There a few things you need to change inside the .tt file. I choose to use the third solution you suggested but this requires to be formatted like FK_CollectionName_RelationName. I split them up with '_' and use the last string in the array.
I use the RelationName with the ToEndMember property to create a property name. FK_Projects_TechnicalContact will result in
//Plularized because of EF.
public virtual Contacts TechnicalContactContacts { get; set; }
and your projects will be like this.
public virtual ICollection<Projects> SalesContactProjects { get; set; }
public virtual ICollection<Projects> TechnicalContactProjects { get; set; }
Now the code you may ask. Ive added 2 functions to the CodeStringGenerator class in the T4 file. One which builds the propertyName recieving a NavigationProperty. and the other one generating the code for the property recieving a NavigationProperty and the name for the property.
//CodeStringGenerator class
public string GetPropertyNameForNavigationProperty(NavigationProperty navigationProperty)
{
var ForeignKeyName = navigationProperty.RelationshipType.Name.Split('_');
var propertyName = ForeignKeyName[ForeignKeyName.Length-1] + navigationProperty.ToEndMember.Name;
return propertyName;
}
public string NavigationProperty(NavigationProperty navigationProperty, string name)
{
var endType = _typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType());
return string.Format(
CultureInfo.InvariantCulture,
"{0} {1} {2} {{ {3}get; {4}set; }}",
AccessibilityAndVirtual(Accessibility.ForProperty(navigationProperty)),
navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType,
name,
_code.SpaceAfter(Accessibility.ForGetter(navigationProperty)),
_code.SpaceAfter(Accessibility.ForSetter(navigationProperty)));
}
If you place the above code in the class you still need to change 2 parts. You need to find the place where the constructor part and the navigation property part are being build up of the entity. In the constructor part (around line 60) you need to replace the existing code by calling the method GetPropertyNameForNavigationProperty and passing this into the escape method.
var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty);
#>
this.<#=code.Escape(propName)#> = new HashSet<<#=typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType())#>>();
<#
And in the NavigationProperties part (around line 100) you also need to replace the code with the following.
var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty);
#>
<#=codeStringGenerator.NavigationProperty(navigationProperty, propName)#>
<#
I hope this helps and you can always debug the GetPropertyNameForNavigationProperty function and play a little with the naming of the property.
Building on BikeMrown's answer, we can add Intellisense to the properties using the RelationshipName that is set in MSSQL:
Edit model.tt in your VS Project, and change this:
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
<#
}
#>
<#=codeStringGenerator.NavigationProperty(navigationProperty)#>
<#
}
}
to this:
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
<#
}
#>
/// <summary>
/// RelationshipName: <#=code.Escape(navigationProperty.RelationshipType.Name)#>
/// </summary>
<#=codeStringGenerator.NavigationProperty(navigationProperty)#>
<#
}
}
Now when you start typing a property name, you get a tooltip like this:
It's probably worth noting that if you change your DB model, the properties may find themselves pointing at different DB fields because the EF generates navigation property names based on their respective DB field name's alphabetic precedence!
Found this question/answer very helpful. However, I didn't want to do as much as Rikko's answer. I just needed to find the column name involved in the NavigationProperty and wasn't seeing how to get that in any of the samples (at least not without an edmx to pull from).
<#
var association = (AssociationType)navProperty.RelationshipType;
#> // <#= association.ReferentialConstraints.Single().ToProperties.Single().Name #>
The selected answer is awesome and got me going in the right direction for sure. But my big problem with it is that it took all of my already working navigation properties and appended the base type name to them, so you'd end up with with things like the following.
public virtual Need UnitNeed { get; set;}
public virtual ShiftEntered UnitShiftEntered {get; set;}`
So I dug into the proposed additions to the .tt file and modified them a bit to remove duplicate type naming and clean things up a bit. I figure there's gotta be someone else out there that would want the same thing so I figured I'd post my resolution here.
Here's the code to update within the public class CodeStringGenerator
public string GetPropertyNameForNavigationProperty(NavigationProperty navigationProperty, string entityname = "")
{
var ForeignKeyName = navigationProperty.RelationshipType.Name.Split('_');
var propertyName = "";
if (ForeignKeyName[ForeignKeyName.Length-1] != entityname){
var prepender = (ForeignKeyName[ForeignKeyName.Length-1].EndsWith(entityname)) ? ReplaceLastOccurrence(ForeignKeyName[ForeignKeyName.Length-1], entityname, "") : ForeignKeyName[ForeignKeyName.Length-1];
propertyName = prepender + navigationProperty.ToEndMember.Name;
}
else {
propertyName = navigationProperty.ToEndMember.Name;
}
return propertyName;
}
public string NavigationProperty(NavigationProperty navigationProperty, string name)
{
var endType = _typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType());
var truname = name;
if(navigationProperty.ToEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many){
if(name.Split(endType.ToArray<char>()).Length > 1){
truname = ReplaceLastOccurrence(name, endType, "");
}
}
return string.Format(
CultureInfo.InvariantCulture,
"{0} {1} {2} {{ {3}get; {4}set; }}",
AccessibilityAndVirtual(Accessibility.ForProperty(navigationProperty)),
navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType,
truname,
_code.SpaceAfter(Accessibility.ForGetter(navigationProperty)),
_code.SpaceAfter(Accessibility.ForSetter(navigationProperty)));
}
public static string ReplaceLastOccurrence(string Source, string Find, string Replace)
{
int place = Source.LastIndexOf(Find);
if(place == -1)
return Source;
string result = Source.Remove(place, Find.Length).Insert(place, Replace);
return result;
}
and here's the code to update within the model generation,
update both occurrences of this:
var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty)
to this
var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty, entity.Name);