Trouble with Code First DatabaseGenerated Composite Primary Key - c#

This is a tad complicated, and please, I know all the arguments against natural PK's, so we don't need to have that discussion.
using VS2012/MVC4/C#/CodeFirst
So, the PK is based on the date and a corresponding digit together. So, a few rows created today would be like this:
20131019 1
20131019 2
And one created tomorrow:
20131020 1
This has to be automatically generated using C# or as a trigger or whatever. The user wouldn't input this. I did come up with a solution, but I'm having problems with it, and I'm a little stuck, hence the question.
So, I have a model:
public class MainOne
{
//[Key]
//public int ID { get; set; }
[Key][Column(Order=1)]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string DocketDate { get; set; }
[Key][Column(Order=2)]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string DocketNumber { get; set; }
[StringLength(3, ErrorMessage = "Corp Code must be three letters")]
public string CorpCode { get; set; }
[StringLength(4, ErrorMessage = "Corp Code must be four letters")]
public string DocketStatus { get; set; }
}
After I finish the model, I create a new controller and views using VS2012 scaffolding.
Then, what I'm doing is debugging to create the database, then adding the following instead of trigger after Code First creates the DB [I don't know if this is correct procedure]:
CREATE TRIGGER AutoIncrement_Trigger ON [dbo].[MainOnes]
instead OF INSERT AS
BEGIN
DECLARE #number INT
SELECT #number=COUNT(*) FROM [dbo].[MainOnes] WHERE [DocketDate] = CONVERT(DATE, GETDATE())
INSERT INTO [dbo].[MainOnes] (DocketDate,DocketNumber,CorpCode,DocketStatus) SELECT (CONVERT(DATE, GETDATE
())),(#number+1),inserted.CorpCode,inserted.DocketStatus FROM inserted
END
And when I try to create a record, this is the error I'm getting:
The changes to the database were committed successfully, but an error occurred while updating the object context. The ObjectContext might be in an inconsistent state. Inner exception message: The object state cannot be changed. This exception may result from one or more of the primary key properties being set to null. Non-Added objects cannot have null primary key values. See inner exception for details.
Now, what's interesting to me, is that after I stop debugging and I start again, everything is perfect. The trigger fired perfectly, so the composite PK is unique and perfect, and the data in other columns is intact.
My guess is that EF is confused by the fact that there is seemingly no value for the PK until AFTER an insert command is given. Also, appearing to back this theory, is that when I try to edit on of the rows, in debug, I get the following error:
The number of primary key values passed must match number of primary key values defined on the entity.
Same error occurs if I try to pull the 'Details' or 'Delete' function.
Any solution or ideas on how to pull this off? I'm pretty open to anything, even creating a hidden int PK. But it would seem redundant.
EDIT 21OCT13
[HttpPost]
public ActionResult Create(MainOne mainone)
{
if (ModelState.IsValid)
{
var countId = db.MainOnes.Count(d => d.DocketDate == mainone.DocketNumber); //assuming that the date field already has a value
mainone.DocketNumber = countId + 1; //Cannot implicitly convert type int to string
db.MainOnes.Add(mainone);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(mainone);
}
EDIT 21OCT2013 FINAL CODE SOLUTION
For anyone like me, who is constantly searching for clear and complete solutions.
if (ModelState.IsValid)
{
String udate = DateTime.UtcNow.ToString("yyyy-MM-dd");
mainone.DocketDate = udate;
var ddate = db.MainOnes.Count(d => d.DocketDate == mainone.DocketDate); //assuming that the date field already has a value
mainone.DocketNumber = ddate + 1;
db.MainOnes.Add(mainone);
db.SaveChanges();
return RedirectToAction("Index");
}

hy don't you manage it via code instead of using a trigger?
var countId = context.MainOne.Count(d => d.DocketDate == newItem.DocketNumber); //assuming that the date field already has a value
newItem.DocketNumber = CountId + 1;
Context.MainOne.Add(newItem);
context.SaveChanges();
And this should solve your problem.

Related

Null foreign key in database - but why?

I have a database full of vehicles. Each vehicle can have many pictures. Obviously, the pictures table has a Vehicle foreign key.
Whenever I add a picture, then try to retrieve it, I cannot. This is due to the fact that the Vehicle foreign key is returned as Null. Trouble is, I don't understand why. At this point in time, understand that on a database level, all the fields in the picture table are populated as expected. There are 3 fields Id (PK, int), Picture (nVarChar(max)) and VehicleId(FK, int)). Crucially, the foreign key (VehicleId) IS populated with a valid vehicle Id.
I am using the MVC.
First of all, code for the Picture Model.
public class Picture
{
public int Id {get; set;}
public string Image {get; set;}
[ForeignKey("VehicleId")]
public virtual Vehicle Vehicle{get;set;}
}
Secondly, Picture Controller code that saves a picture. Notice how I am setting the Vehicle Foreign Key.
IMPORTANT Clue - If I put test code in Immediately before 'return Created' to retrieve a picture, I can. Vehicle Foreign Key appears to be set. If I just run the application and try to retrieve a picture straight away (i.e withoud adding a new one), the Vehicle Foreign key is null. Why though? I have included the code I call that retrieves an individual picture.
public async Task<IActionResult> PostNewPicture(int vehicleId_, PictureViewModel picture_)
{
try
{
if(ModelState.IsValid)
{
var vehicle = _vehicleRepository.GetVehicleById(vehicleId_);
if (vehicle == null) return BadRequest("Vehicle Not Found");
var newPicture = _mapper.Map<Picture>(picture_);
newPicture.Vehicle = vehicle;
_vehicleRepository.AddPicture(newPicture);
if (await _vehicleRepository.SaveChangesAsync())
{
var url = _linkGenerator.GetPathByAction("GetIndividualPicture",
"Pictures",
new {vehicleId_ = newPicture.VehicleForeignKey.Id,
pictureId_ = newPicture.Id});
var pictureViewModel = _mapper.Map<PictureViewModel>(newPicture);
return Created(url, _mapper.Map<PictureViewModel>(newPicture));
}
}
return BadRequest("Failed to save the picture");
}
catch(Exception ex)
{
return BadRequest($"Exception Thrown : {ex}");
}
}
Code to retrieve an individual picture:
public Picture GetIndividualPicture(int vehicleId_, int pictureId_)
{
_vehicleContext.Pictures.Include(vp => vp.Vehicle);
IEnumerable<Picture> pictures = from v in _vehicleContext.Pictures.ToList()
.Where(x => x.Vehicle.Id == vehicleId_
&& x.Id == pictureId_) select v;
return pictures.FirstOrDefault();
}
Why VehicleForeignKey is Null, when it is clearly set at the point of adding?
Kudos to Gert Arnold for this one. He correctly states
the second code snippet. You don't have _vehicleContext.VehiclePictures.Include(vp => vp.VehicleForeignKey)
I feel like a bit of a fraud answering this myself - again, It was Gert. Here are my check in notes on this....
12th November 2019
Pictures Controller - I could not understand why the foreign Key withing the Pictures
table was always being returned as null. The answer was because I was not Including it.
See Include in the query below. Dont include this and Vehicle is always null.
ALSO - I was using .ToList() immediately after the .Include(vp => vp.Vehicle)
dont do this - it loads the entire result set into memory BEFORE any filtering is done
public Picture GetIndividualPicture(int vehicleId_, int pictureId_)
{
IEnumerable<Picture> pictures = from v in _vehicleContext.Pictures
.Include(vp => vp.Vehicle)
.Where(x => x.Vehicle.Id == vehicleId_
&& x.Id == pictureId_) select v;
return pictures.FirstOrDefault();
}

Entity Framework claims to save changes but sometimes doesn't

I have a very strange problem - sometimes my database fails to populate with Plans and Buckets, then throws the exception shown at the end when trying to save Tasks to the db. Other times the code works perfectly. I would appreciate any insight into why this could be. Could it be a timing issue with the delete commands?
EntitiesModelContainer db = new EntitiesModelContainer();
List<Plan> ignoredPlans = db.Plans.Where(p => p.Ignore).ToList();
// Delete old data completely
// foreign keys will cascade delete some tables such as buckets and tasks
db.Database.ExecuteSqlCommand("DELETE FROM Plans");
db.Database.ExecuteSqlCommand("DELETE FROM Assignees");
PlansResponse plans = GraphAPI.GetPlans();
foreach (PlanResponse plan in plans.Plans)
{
Plan planRecord = new Plan()
{
Id = plan.ID,
Title = plan.Title,
Ignore = ignoredPlans.Find(p => p.Id == plan.ID) != null
};
db.Plans.Add(planRecord);
bool changes = db.ChangeTracker.HasChanges();
int result = db.SaveChanges();
if (!planRecord.Ignore)
{
BucketsResponse buckets = GraphAPI.GetBuckets(plan);
foreach (BucketResponse bucket in buckets.Buckets)
{
Bucket bucketRecord = new Bucket()
{
Id = bucket.ID,
Name = bucket.Name,
PlanId = bucket.PlanID
};
db.Buckets.Add(bucketRecord);
db.SaveChanges();
}
TasksResponse tasks = GraphAPI.GetTasks(plan);
foreach (TaskResponse task in tasks.Tasks)
{
Task taskRecord = new Task()
{
Id = task.ID,
Title = task.Title,
Progress = task.PercentComplete,
Description = task.HasDescription ? GraphAPI.GetTaskDetails(task).Description : null,
CreatedDateTime = task.CreatedDateTime,
DueDateTime = task.DueDateTime,
CompletedDateTime = task.CompletedDateTime,
PlanId = task.PlanID,
BucketId = task.BucketID
};
db.Tasks.Add(taskRecord);
db.SaveChanges();
The last line is where the error occurs.
SqlException: The INSERT statement conflicted with the FOREIGN KEY constraint "FK_PlanTask". The conflict occurred in database "Dashboard-dev", table "dbo.Plans", column 'Id'. The statement has been terminated.
The strange thing is, it doesn't always throw! Any help would be appreciated.
Update: You're right - it appears the code is running twice, concurrently. Why? I didn't (intentionally) mean to do this...
public class DBController : Controller
{
public ActionResult Index()
{
DBAccess.UpdateData();
return View();
}
}
The snippet posted above is the start of this method UpdateData().
Since it's complaining about the foreign key PlanId (I'm guessing), I'd say GetTasks somehow returns an incorrect PlanId.
To get to the bottom of it I'd set up a profiler session and examine the SQL statements when there's an error. Or just examine the objects in the debugger when the exception happens, I guess. The values of the properties should give a clue as to what's going on.
Edit: I just noticed your first line says 'sometimes it fails to populate with plans and tasks', missed that. I don't understand how it would continue if it couldn't save a plan, but a SQL profiler session might be able to answer that.
Edit after the fact: of course the simplest answer could be concurrency, especially if this is a web application, two requests could be coming in at the same time and overlapping.
Can tasks have a null PlanID (why it might work sometimes)? The delete at the top looks suspicious, shouldn't you be setting the PlanID to the newly created planRecord? Either way you are setting it to a Plan that no longer exists so a cascade might not be working somewhere ( if for example it was supposed to nullify dead plans as foreign keys to Task and Bucket).

The property 'name' is part of the object's key information and cannot be modified. Entity Framework

I am trying to update a record and I get this error message after the context.SaveChanges();
The property 'name' is part of the object's key information and cannot be modified.
Here is the code for the update function:
if (context.EAT_SourceNames.Any(e => e.name == newSourceName))
{
MessageBox.Show("Name already exists in the Database");
}
else
{
var nameToUpdate = context.EAT_SourceNames.SingleOrDefault(e => e.name == sourceName.name);
if (nameToUpdate != null)
{
nameToUpdate.name = newSourceName;
context.SaveChanges();
RefreshDGVs();
}
}
My SourceNames class looks like the following:
public EAT_SourceNames()
{
this.EAT_Sources = new ObservableListSource<EAT_Sources>();
}
public string name { get; set; }
public string version_id { get; set; }
public string allocation_name { get; set; }
I searched for similar questions, but could not find any working solution.
See the answer from yildizm85 to this question: entity framework not working on table without identity column
"Entity Framework requires a Primary Key to generate a model from the database. If there is no Primary Key on a table it will simply select the non-nullable columns as a concatenated primary key and the Entity will be read/only."
Looking at your EAT_SourceNames object it appears there is no primary key field so the Entity Framework is using the column 'name' as part of the composite key which means it is read-only.
The solution would be to add a Primary Key field to EAT_SourceNames and then your 'name' field would no longer be part of the primary key.
Same happened to me today. I set new entity's ID with the old record's ID and the error is gone.
//This checks whether there's a record with same specific data.
var kayitVarMi = _db.Sorgu.FirstOrDefault(a => a.Serial == sorgu.Serial);
if (kayitVarMi != null) // If there's
{
sorgu.Id = kayitVarMi.Id; //This one does the trick
_db.Entry(kayitVarMi).CurrentValues.SetValues(sorgu);
}
else // If not
{
_db.Sorgu.Add(sorgu);
}
// This whole block is in a transaction scope so I just check recordability.
if (_db.SaveChanges() > 0)
{
_sorguKaydedildiMi = true;
}
Here is the proper solution of it.
You have to unchecked entity key of Name from your EAT_SourceNames.
You can do this by following steps.
Open your .edmx file.
Right click on your display and select Mapping Details.
Click on Model Browser and you will find it at right side of your screen.
Now In that go to Entity Types folder and click on your table in your case EAT_SourceName.
Now you will find the model of EAT_SourceName , now right click on name and uncheck Entity Keys.
Now Clean and Rebuild your Solution.
The only way I can think to update a text primary key is by using the following.
I do not believe it is best practice to use a "functional" primary key. A primary key's purpose is simply to uniquely identify a row.
context.Database.ExecuteSqlCommand("UPDATE Table SET [Name] = {0} WHERE [Name] = {1}", nameProperty, oldNameProperty);
Probably name is a part or full Primary Key for your EAT_SourceNames entity.
You cannot modify object's PK, is it EF's limitation (see this thread).
The point is that you work with an object. The "name" property identifies the object, that's why you can't modify it.
The solution is to modify the value in the table with a SQL statement (UPDATE) and reload the context.
Sincerely.

Error in CodeFirst Seed with migrations : Modifying a column with the 'Identity' pattern is not supported. Column: 'CreatedAt'.

I have activated migrations on my Azure Mobile Services project. I filled the new seed function Inside the Configuration.cs class of the migrations. If the tables are empty, the seed function is going without any problems. When my AddorUpdate tries to update the first object I get the error in the inner exception : "Modifying a column with the 'Identity' pattern is not supported. Column: 'CreatedAt'. Table: 'CodeFirstDatabaseSchema.Category'."
Part of my code is as follows:
context.categories.AddOrUpdate(
new Category { Id="1", Code="GEN", Text="General"},
new Category { Id="2", Code="POL", Text="Politics"},
new Category { Id="3", Code="FAS", Text="Fashion"},
new Category { Id="4", Code="PEO", Text="People"},
new Category { Id="5", Code="TEC", Text="Technology"},
new Category { Id="6", Code="SPO", Text="Sport"},
new Category { Id="7", Code="LIV", Text="Living"}
);
Here's my generic implementation of Nikatlas' solution.
Short version of the answer: You can't modify CreatedAt with a null value, so you can use this function instead:
private void AddOrUpdatePreservingCreatedAt<T> (DbSet<T> set, T item) where T : EntityData
{
var existing = set.Where(i => i.Id == item.Id).FirstOrDefault();
if (existing != null)
{
item.CreatedAt = existing.CreatedAt;
}
set.AddOrUpdate(i => i.Id, item);
}
Call it like this
AddOrUpdatePreservingCreatedAt(context.YourItems, itemToBeUpdatedOrAdded);
It seems i have found a solution to this problem.
The reason this error occurs is because of the AddOrUpdate Method.
As stated in this post : http://thedatafarm.com/data-access/take-care-with-ef-4-3-addorupdate-method/
More importantly, if a match is found then the update will update all and null out any that weren’t in your AddOrUpdate.
What this means is that after the first seed, whenever your code runs it tries to update your entities correctly but it tries to pass the value null on CreatedAt field. If you look at EntityData class the CreateAt field has these attributes :
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] //Here they mark this as IDENTITY
[Index(IsClustered = true)] // Cluster index. i really dont know why ?
[TableColumn(TableColumnType.CreatedAt)]
public DateTimeOffset? CreatedAt { get; set; }
So the error occurs beacause you try to modify the CreatedAt column.
My solution was to Create a List, lookup to set CreatedAt to correct value and then addOrUpdate:
// Create List
List<Permission> permissions = new List<Permission>(new Permission[]{
new Permission { Id = "ID1" , Name = "Send SMS"},
new Permission { Id = "ID2", Name = "Send Email"} });
// Iterate through list to set CreatedAt to correct value
foreach (Permission p in permissions){
// Get the record from the db if it exists
var t = context.PermissionSet.Where(s => s.Id == p.Id).FirstOrDefault();
if (t != null){
p.CreatedAt = t.CreatedAt; //SET CreatedAt to correct Value if the record already exists
}
context.PermissionSet.AddOrUpdate(a => a.Id, p); // NOW I CAN UPDATE WITH NO PROBLEM
}
Hope this helps. :)
If you have a integer column named Id, then Entity Framework will assume that is the primary key and that it is database generated - so it is created as an IDENTITY column in the database.
You cannot specify the Id for IDENTITY columns, so you stop doing that by removing the Id = 1, Id = 2, etc
I am a bit thrown by the fact that the column you have a problem with is named "CreatedAt". It sounds like it should be a DateTime and might also be database generated, but it surely shouldn't be IDENTITY?
Anyways, the usage you probably want is the one where you specify the natural key of the entity, so that EF can identify any records that already exist. So, if CODE is the natural key then you should be writing the Seed like this:
context.categories.AddOrUpdate(
x => x.Code,//the natural key is Code
new Category { Code="GEN", Text="General"},
new Category { Code="POL", Text="Politics"},
new Category { Code="FAS", Text="Fashion"},
new Category { Code="PEO", Text="People"},
new Category { Code="TEC", Text="Technology"},
new Category { Code="SPO", Text="Sport"},
new Category { Code="LIV", Text="Living"}
);
Reference:
Take care with the AddOrUpdate method
This question, its answers and comments might help you a bit, but not much.
You can do Inserts on identity column using the solutions provided by the question. But you cannot update values of Identity Column. The only way to do updates on identity column is to mark it as not being identity. Probably by adding manual migrations.
This SO question and its answers may also be helpful.
Read here also on general MSSQL Server constraints on Updating Identity Column.
SAME PROBLEM AND SOLUTION
We had the same problem and it was a trigger in another table that had impact on the table that we had the problem on.
DETAILS OF OUR DEVELOPMENT
We developed a Xamarin App connected to an Azure Web Service. When we used the method PushAsync from iMobileServices, it gave us the error: Modifying a column with the 'Identity' pattern is not supported. Column: 'CreatedAt'.
It was strange for us as some tables did not have the problem with the web service
REASON
It seems that the trigger update got in conflict with the pushasync from the mobile device.
We disabled the trigger, switched the responsibility to the front end and it worked fine. At least for us.
We hope this soltion helps someone.

What is the correct way of using Entity Framework?

I have a DB like this that I generated from EF:
Now I'd like to add a "fielduserinput" entity so I write the following code:
public bool AddValueToField(string field, string value, string userId)
{
//adds a value to the db
var context = new DBonlyFieldsContainer();
var fieldSet = (from fields in context.fieldSet
where fields.fieldName.Equals(field)
select fields).SingleOrDefault();
var userSet = (from users in context.users
where users.id.Equals(userId)
select users).SingleOrDefault();
var inputField = new fielduserinput { userInput = value, field = fieldSet, user = userSet };
return false;
}
Obviously it's not finished but I think it conveys what I'm doing.
Is this really the right way of doing this? My goal is to add a row to fielduserinput that contains the value and references to user and field. It seems a bit tedious to do it this way. I'm imagining something like:
public bool AddValueToField(string userId, string value, string fieldId)
{
var context = new db();
var newField = { field.fieldId = idField, userInput = value, user.id = userId }
//Add and save changes
}
For older versions of EF, I think you're doing more or less what needs to be done. It's one of the many reasons I didn't feel EF was ready until recently. I'm going to lay out the scenario we have to give you another option.
We use the code first approach in EF 4 CTP. If this change is important enough, read on, wait for other answers (because Flying Speghetti Monster knows I could be wrong) and then decide if you want to upgrade. Keep in mind it's a CTP not an RC, so considerable changes could be coming. But if you're starting to write a new application, I highly recommend reading some about it before getting too far.
With the code first approach, it is possible to create models that contain properties for a reference to another model and a property for the id of the other model (User & UserId). When configured correctly setting a value for either the reference or the id will set the id correctly in the database.
Take the following class ...
public class FieldUserInput{
public int UserId {get;set;}
public int FieldId {get;set;}
public virtual User User {get;set;}
public virtual Field Field {get;set;}
}
... and configuration
public class FieldUserInputConfiguration{
public FieldUserInputConfiguration(){
MapSingleType(fli => new {
userid = fli.UserId,
fieldid = fli.FieldId
};
HasRequired(fli => fli.User).HasConstraint((fli, u)=>fli.UserId == u.Id);
HasRequired(fli => fli.Field).HasConstraint((fli, f)=>fli.FieldId == f.Id);
}
}
You can write the code...
public void CreateField(User user, int fieldId){
var context = new MyContext();
var fieldUserInput = new FieldUserInput{ User = user, FieldId = fieldId };
context.FieldUserInputs.Add(fieldUserInput);
context.SaveChanges();
}
... or vice versa with the properties and everything will work out fine in the database. Here's a great post on full configuration of EF.
Another point to remember is that this level of configuration is not necessary. Code first is possible to use without any configuration at all if you stick to the standards specified in the first set of posts referenced. It doesn't create the prettiest names in the database, but it works.
Not a great answer, but figured I'd share.

Categories