Strange entity updating in Entity Framework Code-First - c#

I'm solving the problem with updating entity before saving to database and got strange behavior.
I'm using Entity Framework 4.1 Code-First in ASP.NET MVC 3 web application. Here is model:
public class Order
{
public int OrderId { get; set; }
public int CarId { get; set; }
public DateTime BeginRentDate { get; set; }
public DateTime EndRentDate { get; set; }
public decimal RentPrice { get; set; }
public virtual Car Car { get; set; }
}
public class Car
{
public int CarId { get; set; }
public string Brand { get; set; }
public string Model { get; set; }
public string NumberPlate { get; set; }
public decimal RentPrice { get; set; }
}
Each Car has a RentPrice. This price should be copied to Order's RentPrice when creating one. The car is selecting by user so initially Order.RentPrice is 0.
Here I want to copy price value:
[HttpPost]
public ActionResult Create(Order order)
{
order.RentPrice = _context.Cars.Find(order.CarId).RentPrice;
if (ModelState.IsValid)
{
_context.Orders.Add(order);
_context.SaveChanges();
return RedirectToAction("Index");
}
return View(order);
}
It's not working because of an error on the SaveChanges that entity has validation errors. OK. I found that need first to call UpdateModel(order); and then change values.
So what I have. Working code:
_context.Orders.Add(order);
UpdateModel(order);
order.RentPrice = 777;
_context.SaveChanges();
Not working code:
_context.Orders.Add(order);
UpdateModel(order);
order.RentPrice = _context.Cars.Find(order.CarId).RentPrice;
_context.SaveChanges();
Working code (!):
_context.Orders.Add(order);
UpdateModel(order);
var t = (double)_context.Cars.Find(order.CarId).RentPrice;
order.RentPrice = (decimal)t;
_context.SaveChanges();
Can someone explain, please, what is going on here? Especially magic on the 3nd and 4th lines in the last block of code.
Update
I'm getting DbEntityValidationException: "Validation failed for one or more entities. See 'EntityValidationErrors' property for more details."
From the inner exception: "OriginalValues cannot be used for entities in the Added state."

When you get
"Validation failed for one or more entities. See
'EntityValidationErrors' property for more details." From the inner
exception: "OriginalValues cannot be used for entities in the Added
state."
It means there was errors such as NOT NULL collumns that were blank or other constraints , check the entity validation errors by debugging or like
try{
...
catch ( DbEntityValidationException ex )
{
foreach ( var validationErrors in ex.EntityValidationErrors )
{
foreach ( var validationError in validationErrors.ValidationErrors )
{
System.Diagnostics.Trace.TraceInformation( "Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage );
}
}
}

OriginalValues cannot be used for entities in the Added state.
Can be corrected by ensuring that non identity primary key fields are specified as not generated in the model.
modelBuilder
.Entity<T>()
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
// this can also be achieved using attributes on the entity.
The error is actually self explanatory when you have context, but baffling otherwise. The database generates two statements for the insert, the second of which is:
SELECT [PrimaryKeyId]
FROM [dbo].[Entity]
WHERE ##ROWCOUNT > 0 AND [PrimaryKeyId] = scope_identity()
/* SP:StmtCompleted... */
This statement will not return any rows for non identity columns. Hence the OriginalValues will remain unaltered in the added state. This also explains why this exception is wrapped in an OptimisticConcurrencyException as the number of rows affected is used to detect existing altered data.

Have you declared the primary key anywhere? You should do that either using FluentAPI or attribute like this:
public class Order
{
[Key]
public int OrderId { get; set; }
public int CarId { get; set; }
public DateTime BeginRentDate { get; set; }
public DateTime EndRentDate { get; set; }
public decimal RentPrice { get; set; }
public virtual Car Car { get; set; }
}
public class Car
{
[Key]
public int CarId { get; set; }
public string Brand { get; set; }
public string Model { get; set; }
public string NumberPlate { get; set; }
public decimal RentPrice { get; set; }
}
Or in your Context, you can use Fluent API to declare the key, like this
builder.Entity<Car>().HasKey(x=>x.CarId);
builder.Entity<Order>().HasKey(x=>x.OrderId);

I have an idea, but it's more of a guess... Maybe that by the time you get in that Create function, the context already knows of that Order? If it does, trying to re-add it again could maybe give you that error.
In your last piece of code (the one that surprisingly work), have you tried to use an intermediary var without casting to double?
I'm also intrigued of that UpdateModel... I use EF but not MVC so maybe it has nothing to do about it, but if it's a custom function, what does it do?

I am sure the problem would have been rectified ages ago, just wanted to contribute my 2 cents.
I also ran into an issue with the same error message, I was using Oracle and EF 5.0. Finally I got a solution after wasting more than 12 hours.
For me it was lack of the permissions for the user on the table where I was trying to insert values and the error message absolutely had no hints of the actual permission issue, which was really weird.
Hope someone finds this useful and doesn't waste hours like me in troubleshooting.
Cheers,
Avi

Related

Insight.Database - using Insert with OpenWithTransaction

First question ever :)
I'm a newbie at using the Insight.Database library but I think I'm using this in the right way. I'm running the (simplified) code below from a XUnit Test in another project in the same solution. The exception is thrown on the line with the connTran.Insert, and if I break on the logging function in the CATCH block and look at the message in the exception, it gives the error error CS7069: Reference to type 'DbConnectionWrapper' claims it is defined in 'Insight.Database', but it could not be found, but then the debugger will also break on the connTran.Rollback() line with A transaction has not been created for this connection.
What's weird is that I have used the same code in another Test in the same solution and test project but with a flat entity and it operated fine.
I'm using Visual Studio 2015 Enterprise. The debugger is also not behaving properly - the hovering over variables, etc doesn't work while "inside" the transaction. I've found a very similar github issue on Github here but no resolution that I can use.
This is the Insert code I am using - I have also tried connTran.Insert but I get the same result.
DbConnectionWrapper connTran = null;
try
{
using (connTran = _dbconnection.OpenWithTransaction())
{
var lookupHeader = connTran.QueryResults<Results>("usp_Lookup_InsertHeader", entity);
connTran.Commit();
}
}
catch(Exception ex)
{
logException(ex.Message);
connTran.Rollback();
throw;
}
The entity object looks like this:
public class LookupEntity
{
[RecordId]
public int LookupHeaderId { get; set; }
public string LookupHeaderName { get; set; }
public string LookupHeaderDescription { get; set; }
public string LookupHeaderCategory { get; set; }
public string LookupHeaderType { get; set; }
public string LookupBPMObjectName { get; set; }
public string LookupBPMMethodName { get; set; }
public string LookupBPMInputParams { get; set; }
public string LookupBPMExtraParams { get; set; }
public string LookupBPMOutputDataSetName { get; set; }
public string LookupBPMOutputNameNode { get; set; }
public string LookupBPMOutputValueNode { get; set; }
public string LookupBPMOutputActiveNode { get; set; }
public int Active { get; set; }
public int Cache { get; set; }
public int CsysLastUpdateBy { get; set; }
public DateTime? CsysLastUpdateDate { get; set; }
public int CsysInsertBy { get; set; }
public DateTime? CsysInsertDate { get; set; }
public string CsysTimeStamp { get; set; }
public string CsysTag { get; set; }
public int CsysOwnerId { get; set; }
public string CsysOwnerType { get; set; }
public int CsysRecordStatus { get; set; }
[ChildRecords]
public List<LookupDetail> LookupDetails { get; set; }
}
Well...a bit more messing around and poking through the Insight source code, I got past my original issue, and encountered a few more. I'm going to post my findings here in case the Insight author has a look.
The answer to my original issue was to restructure my TRY..CATCH to be inside the USING - this may have been obvious, maybe it wasn't but now I know :) So my code turned into this:
using (var connTran = _dbconnection.OpenWithTransaction())
{
try
{
connTran.Insert("usp_Lookup_InsertHeader", entity, new { lookupHeader = entity });
connTran.Commit();
}
catch(Exception ex)
{
logException(ex.Message);
connTran.Rollback();
throw;
}
}
Note I could also get rid of declaring the connTran variable outside of the using.
Since I'm using SQL 2008, I was keen to use the Table Value Parameters with the Insert stored procedure. This gave me my next head-scratcher - which may be related to out-of-date documentation.
The doco states that code like this:
connTran.Insert("usp_Lookup_InsertHeader", entity);
would insert the record and then map the new identity value back into the entity, assuming the returned id field and entity property names match up (which they do). The Insert stored procedure has a signature like this:
CREATE PROCEDURE [dbo].[usp_Lookup_InsertHeader]
#lookupHeader [lookupHeaderType] READONLY
Insight kept complaining that the "lookupHeader" parameter was not defined, so I eventually stumbled across something elsewhere in the doco that turned my code into this:
connTran.Insert("usp_Lookup_InsertHeader", entity, new { lookupHeader = entity });
Now Insight is happy :)
The third issue then became datetime values.
The CsysLastUpdateDate property in the entity was defined as DateTime?. In the SQL Type for the TVP, the CsysLastUpdateDate field was defined as DateTime. In my Test, I set the CsysLastUpdateDate to DateTime.Now
Watching SQL Profiler, I found that the SQL text Insight was posting included the milliseconds in what is now a string representation of the datetime value. There is a sample of this text below with the string datetime - '2016-06-16 18:03:32.5510000'.
declare #p1 dbo.lookupHeaderType
insert into #p1 values(0,N'timbo.test',N'',N'',N'',N'',N'',N'',N'',N'',N'',N'',N'',1,1,2,'2016-06-16 18:03:32.5510000',2,'2016-06-16 18:03:32.5510000',N'',N'',1,N'cSysSite',1)
When SQL tried to execute that text to create the TVP, it errored with a datetime conversion issue. If I manually edited the milliseconds out of the datetime string and executed the text, the TVP created properly.
With some more playing around I discovered that declaring the CsysLastUpdateDate field in the Type as a DateTime2 field, the SQL that Insight was sending executed aok and the Insert worked happily.
I'm not sure if I've found bugs or these are just newbie learnings, but I hope this helps the next person :)

Violation of primary keys when creating a new record with Entity Framework

Utter greenhorn here! It has already devoured me a few hours, but - I hope - for you it will be a piece of cake. I have had to first create a database (containing a single table book) and then automatically implement a basic CRUD functionality with Entity Framework. The table does have its primary key defined!
My problem apparently lies in the way IDs are being generated. Starting with an empty database, I got no exception adding the first record, but cannot add the next one and the db.SaveChanges() instruction within Create function of BookController throws DbUpdateException:
[HttpPost]
public ActionResult Create(book book)
{
if (ModelState.IsValid)
{
db.book.Add(book);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(book);
}
The internal exception goes as follows:
Violation of PRIMARY KEY constraint 'PK_book'. Cannot insert duplicate
key in object 'dbo.book'. The statement has been terminated.
I have changed the part of the generated context by adding two annotations before the ID definition, but it still doesn't work.
public partial class book
{
[Key] // added
[DatabaseGenerated(DatabaseGeneratedOption.Computed)] // added
public int ID { get; set; }
public string title { get; set; }
public string author { get; set; }
public Nullable<int> pages { get; set; }
public Nullable<double> price { get; set; }
public Nullable<System.DateTime> date { get; set; }
public Nullable<bool> access { get; set; }
}
To set auto-increment to your database Key you can set the Identity property to your table definition.
[ID] INT IDENTITY (1, 1) NOT NULL
Or change the ID attribute to:
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }

Invalid Column exception

Hi guys I am getting this exception when I try to run the vehicles page.
Invalid column name 'MakeID'.
I have two models:
public class Vehicle
{
public int VehicleID { get; set; }
public string Title { get; set; }
[ForeignKey("Make")]
public int MakeID { get; set; }
public virtual Make Make { get; set; }
}
public class Make
{
public int MakeID { get; set; }
public string Title { get; set; }
}
The error is being thrown in my vehicles controller here
public ActionResult Index()
{
var vehicles = db.Vehicles.Include(v => v.Make);
return View(vehicles.ToList());
}
Add/Enable Migrations. Update the Database. Most of the case improper update causes the issue. There is no problem with your above code.
Make sure: you have Updated the Database Tables after adding new columns.
Run the Update-Database command in Package Manager Console.
Make sure your column Foreign Key Relationship / Mappings are ok.
You have to check it out manually.
There are four ways to Drop and Create the Database. Based on several conditions. OR you can manually delete the Database from SQL Server.
Good Article and Source can be found here - Programatically.
Note: Recreating the Database will lose the entire existing data. There is one way to avoid this problem. It's providing Seed Data to the database / Tables on create.

How do I use EF6 with Database First and existing views?

I'm an EF noob (any version) and my Google-foo has failed me on finding out how to do this. Which makes me think I must be doing this wrong, but here is the situation:
I'm definitely in an environment that is database first and the schema won't be updated by us coders. I'm also not a fan of 'automatic' code generation, so I've stayed away from the designer or the EF powertools (though I did run through them just to see them work).
To learn I imported the Northwind DB into my LocalDB to have something to play with while creating some simple Web API 2 endpoints. This all went well as I created slimmed down models of the Employees, Shippers, & Region tables in Northwind. Region was particularly interesting as it wasn't plural and EF had issues with that. Anyway, I got by that.
My trouble now is; I want to use a view instead of a table as my source and whatever I'm doing just doesn't seem to work. What I tried was setting it up just like I did the tables. But that produces a ModelValidationException error. I tried looking at the auto-generated code from the designer, but got no insight.
My models:
//-- employee, shipper, & region work as expected
public class employee {
public int EmployeeID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
public class shipper {
public int ShipperID { get; set; }
public string CompanyName { get; set; }
public string Phone { get; set; }
}
public class region {
public int RegionID { get; set; }
public string RegionDescription { get; set; }
}
//-- invoice is a view (actual viewname is 'Invoices')
//-- so i followed the same rules as i did for employee & shipper
//-- i have tried uppercase 'I' as well as a plural version of the model
public class invoice {
public string CustomerID { get; set; }
public string CustomerName { get; set; }
public string Salesperson { get; set; }
public int OrderID { get; set; }
public int ProductID { get; set; }
public string ProductName { get; set; }
}
My Context looks like this:
public class NorthwindDBContext : DbContext {
public DbSet<Employee> Employees { get; set; }
public DbSet<shipper> Shippers { get; set; }
public DbSet<region> Regions { get; set; }
public DbSet<Invoice> Invoices { get; set; } //-- offending line of code
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//--- fix for Region being singular instead of plural
modelBuilder.Entity<region>().ToTable("Region");
}
}
If I comment out the public DbSet<Invoice> Invoices { get; set; } line in the context everything works. Just by having the line present (even if i don't reference the Invoices property) I receive the ModelValidationException error when using the context in anyway.
Can anybody tell me what I'm doing wrong here?
Thanks.
Update: I tried this in one of my controllers, but I am too noob'ish to know if this is the right path either, though it worked as far as getting records.
using (var dbContext = new NorthwindDBContext()) {
return dbContext.Database.SqlQuery<Invoice>("select * from invoices").ToList();
}
Code-first conventions will look for an ID or InvoiceID property to use as a key. Your Invoice model has neither, while the others do. This is the specific reason your code is failing.
The less-specific one is that you can't have entities in EF which lack a unique key. If you can, have the view define a key. Otherwise, you may still be able to work around the issue.

Updating a detached entity in Entity Framework with related entites

I am developing a solution using EF 5 in VS 2012 and I am puzzled about the correct way to specify entity relationships when adding and updating an entity.
These are my main classes where notifier is a person:
public class Notifier : Person
{
public bool IsValid { get; set; }
public int NotifierTypeID { get; set; }
public virtual NotifierType NotifierType { get; set; }
public int MyCaseID { get; set; }
public virtual MyCase MyCase { get; set; }
}
public abstract class Person
{
public int PersonID { get; set; }
public String Name { get; set; }
}
Notifiers belong to cases
public class MyCase
{
public int MyCaseID { get; set; }
public DateTime DateOfNotification { get; set; }
public virtual ICollection<Notifier> Notifiers { get; set; }
}
and have a type:
public class NotifierType
{
public int NotifierTypeID { get; set; }
public string NotifierTypeName { get; set; }
}
I am exposing the foreign keys between notifiers and cases and notifier types.
The method I use to add/update a notifier is:
using (MyContext dbContext = new MyContext(connectionString))
{
notifier.MyCaseID = MyCaseID;
notifier.NotifierTypeID = notifierView.NotifierTypeID;
// **** the puzzling line ****
notifier.NotifierType = dbContext.NotifierTypes.Find(notifierView.NotifierTypeID);
//dbContext.Database.Log = s => System.Diagnostics.Debug.Write(s);
dbContext.Entry(notifier).State = notifier.PersonID == 0 ? EntityState.Added : EntityState.Modified;
dbContext.SaveChanges();
// save the ID in case it's new
notifierViewReturn.PersonID = notifier.PersonID;
}
I am puzzled by the line after the comment **** the puzzling line **** above. I am specifying the foreign keys explicitly and don't need the this line if I am adding a notifier but I do need it if I am updating the object, otherwise it throws an exception.
The exception is
Message=A referential integrity constraint violation occurred:
The property values that define the referential constraints are not
consistent between principal and dependent objects in the relationship.
Can anyone please explain why this line is needed at all. Thanks
When you update an existing entity, before you make changes the entity already has NotifierType (navigation property) and NotifierTypeID populated. If you then change NotifierTypeID but don't update NotifierType, Entity Framework detects a potential inconsistency (NotifierTypeID != NotifierType.NotifierTypeID) and throws the exception you are getting. This is why you need to set both when updating. When adding, you don't have this issue because only one of the IDs is defined (NotifierTypeID, but not NotifierType.NotifierTypeID), so it just uses that one.
If you want to avoid going to retrieve the notifier type for updates, you should be able to just set it to null instead, and in that case there will be no discrepancy and it can just use the NotifierTypeID that you set:
notifier.MyCaseID = MyCaseID;
notifier.NotifierType = null;
notifier.NotifierTypeID = notifierView.NotifierTypeID;
Hope that helps!

Categories