Entity Framework creates non-existing column in query - c#

I face the problem that EF creates a column in the query that does not exist in the Oracle database table.
The simplified model which is created by EF looks like this (I use DB first approach):
public partial class USER
{
public int ID { get; set; }
public string NAME { get; set; }
public int PROCESS_ID { get; set; }
public virtual PROCESS PROCESS { get; set; }
}
public partial class PROCESS
{
public PROCESS()
{
this.USER = new HashSet<User>();
}
public int ID { get; set; }
public virtual ICollection<USER> USER { get; set; }
}
I set up the foreign key constraint in the oracle sql developer.
When I try to get the Users for a selected Process like this:
var users = context.Users.Where(u => u.PROCESS_ID == 0);
It produces following error:
ORA-00904: "Extent1"."R1": invalid ID
So i took a look on the produced SQL:
SELECT
"Extent1".ID,
"Extent1".NAME,
"Extent1".R1,
FROM DB.USER "Extent1"
WHERE "Extent1".R1 = :p__linq__0
Of course this produces an error because R1 isn't a column in the table. But I can't figure out where it comes from. It seems like EF can't map the foreign key properly thats why it's also missing in the generated SQL query?
Maybe someone has a tip for me :)

To follow up my comment, here is a link to the conventions.
The convention for a foreign key is that it must have the same data type as the principal entity's primary key property and the name must follow one of these patterns:
[navigation property name][principal primary key property name]Id
[principal class name][primary key property name]Id
[principal primary key property name]Id
Your convention [navigation property name]_ID isn't on the list.

Encountered the same error recently while working with Oracle using DevArt provider. Turned out it was caused by a column name being longer than 30 chars. OP mentioned that the model posted in his question is a simplified one so it still may be the case.

Related

Entity Framework Core 6 adding (or referencing when already exists) data in a Many-to-Many relationship

I have been struggling to get a many-to-many relationship up and running. I have followed the Microsoft's example for EF Core 5+. My Join table is getting created and I can add entries to all three tables named: SpecificationTest, SpecificationDeviceSpecificationTest and SpecificationDevice.
I am however having some difficulties populating the join table, when the 'SpecificationDevice' already exists.
The simplified entities look as follows:
public class SpecificationTest
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public ICollection<SpecificationDevice> SpecificationDevices { get; set; }
}
public class SpecificationDevice
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string SerialNumber { get; set; }
public ICollection<SpecificationTest> SpecificationTests { get; set; }
}
The join table is automatically created, but I have added the following in the OnModelCreating method as well, even though it is possibly redundant:
modelBuilder.Entity<SpecificationTest>()
.HasMany(a => a.SpecificationDevices)
.WithMany(a => a.SpecificationTests);
modelBuilder.Entity<SpecificationDevice>()
.HasMany(a => a.SpecificationTests)
.WithMany(a => a.SpecificationDevices);
The DbContext also contains the following DbSets:
public DbSet<SpecificationTest> SpecificationTests { get; set; }
public DbSet<SpecificationDevice> SpecificationDevices { get; set; }
By design, every SpecificationTest can have multiple SpecificationDevices. However, the SpecificationDevice table itself can only contain a single entry per unique device (Unique by combination of Serial and Name).
Adding a new SpecificationDevice works as expected. EF creates the entry in the SpecificatonDevice table, and correctly populates the SpecificationDeviceSpecificationTest join table as well.
The problem I am having is that if the SpecificationDevice being added already exists, I merely want to select the ID of that device and add it to the Join table. Currently it tries to add another entry with the ID I have selected, and then throws the following exception:
Error: System.InvalidOperationException: The instance of entity type 'SpecificationDevice' cannot be tracked because another instance with the key value '{Id: 1}' is already being tracked.
Here is the code I use to try and add the entities. Disclaimer, I use the Repository pattern with a UnitOfWork to do the work, but the code comes down to the same.
Note: The specificationTest.SpecificationDevices collection already contains data at this stage, where the SpecificationDevice can be a new device or an existing device.
var existingDevices = await dbContext.SpecificationDevices.AsNoTracking().ToArrayAsync();
foreach (var specDevice in specificationTest.SpecificationDevices)
{
var existingSpecDevice = existingDevices.FirstOrDefault(a => a.Name.Equals(specDevice.Name) && a.SerialNumber.Equals(specDevice.SerialNumber));
if (existingSpecDevice is not null)
{
specDevice.Id = existingSpecDevice.Id;
}
}
await dbContext.SpecificationTests.AddAsync(specificationTest);
await dbContext.SaveChangesAsync();
I would expect that because I selected the ID of the existing SpecificationDevice, that EF will automatically populate the Join table with the correct values (SpecificationTestId just created and the already existing SpecificationDeviceId) This is not the case and I get the exception in the block quote above.
The approach of selecting the ID above works perfectly when trying to add a one-to-one entry when the unique entry already exists, but not with Many-to-many relationship.
Am I doing something wrong? Am I missing something?

Entity Add/Insert with column equal to identity primary key value

During an insert/add can I make a different column equal to the newly valued primary key that's an identity auto generated value all in one write/save process? I know I can grab after fact and change but that's extra database hits I'm trying to avoid.
public class myDataTableRec
{
public int Id { get; set; } //This is an Identity Primary Key
public string Name { get; set; }
public int PostId { get; set; } //Want this the same as Id when it gets generated
}
myrec = new myDataTableRec;
db.myDataTable.Add(myrec);
db.SaveChanges();
From https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.dbcontext.add?view=efcore-5.0
"Add(TEntity)
Begins tracking the given entity, and any other reachable entities that are not already being tracked, in the Added state such that they will be inserted into the database when SaveChanges() is called.
Use State to set the state of only a single entity."
Since it tracks the entity, you can do the following:
myrec=new myDataTableRec;
db.myDataTable.Add(myrec);
myrec.PostId = myrec.Id
db.SaveChanges();
Edit: If it creates the Id after the Save, the you can always create your own Id (Guid.NewGuid()) and don't let EF generate it.
You have to set the property of StoreGeneratedPattern to identity and then you'll be able to achieve this
myrec = new myDataTableRec;
db.myDataTable.Add(myrec);
db.SaveChanges();
var id = myrec.Id;
In SQL it's not possible to insert an entity and set its generated primary in a foreign key column to itself in one atomic operation. That's only possible if the primary key is not generated, i.e. not an identity column.
However, just as EF is capable of inserting related entities in one operation and setting generated key values in foreign keys on the fly, one might argue that EF could support setting a parent to itself.
Take this entity class (replacing your placeholder name and enhancing it with navigation properties):
class Post
{
public int ID { get; private set; }
public string Name { get; set; }
public int? ParentPostID { get; private set; }
public Post ParentPost { get; set; }
public ICollection<Post> ChildPosts { get; private set; }
}
EF could have chosen to support this scenario:
using var db = new MyContext();
var root = new Post { Name = "Root" };
root.ParentPost = root;
db.Set<Post>().Add(root);
But it doesn't. It tries to insert the entity with a ParentPostID equal to the temporary (negative) ID value. Obviously, that's a FK violation.
To do this cleanly you have to add a transaction and set & save the self reference separately:
using var db = new MyContext();
var root = new Post { Name = "Root" };
db.Set<Post>().Add(root);
using var ts = new TransactionScope();
db.SaveChanges();
root.ParentPost = root;
db.SaveChanges();
ts.Complete();
Note that the parent id has to be nullable. Also note that the navigation property allows setting the parent without ever knowing its key value. Some people like to do these things in a DDD style.

Linq query deleting multiple rows instead of one. Very Simple Query

This should be really simple but I think I'm having possible issues with my model. I have been working with linq over a year and I should have this simple remove easily done. Please help! It's removing both records from the database when I only want one deleted
I have a database table with these properties.
Email, EmployeeName, StoreId
jsch#m.com,Joe Schneider,9
jsch#m.com,Joe Schneider,8
I need to delete Joe Schneider with storeId 9
So I run this simple query and remove process.
var temp2 = difference[i];
var PersonToRemove = db.Permissions.SingleOrDefault(s => s.EmployeeName == temp2 && s.StoreId == Persons.StoreId);
if (PersonToRemove.EmployeeName != null)
{
db.Permissions.Remove(PersonToRemove);
db.SaveChanges();
}
I am assuming one is going to say, hey your model is not right and don't put the name as a key, but I can't just be changing the model because other parts of the app are based on this model and would cause huge breaks. Could you give me advise how to edit the linq query to not delete both records?
model
[Table("Permissions")]
public class Permissions
{
[Key]
public String EmployeeName { get; set; }
public string Department { get; set; }
public int? StoreId { get; set; }
public String Email { get; set; }
}
[Table("Permissions")]
public class Permissions
{
[Key]
public String EmployeeName { get; set; }
}
The problem is here you are defining a primary key which has no length constraint on it. (MaxLength). This leads to EF generate a column with NVARCHAR(MAX). As mentioned here VARCHAR(MAX) columns are not allowed to be primary key. So correct definition should be like below
[Table("Permissions")]
public class Permissions
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)] <--
[MaxLength(255)] // <---
public String EmployeeName { get; set; }
}
Edit: You need to recreate the database in order to associated tables initialized with correct settings.
Edit 2 : Also you may need a DatabaseGenerated(DatabaseGeneratedOption.None) since its not identity column.
you can set Deleted state on individual entity like so:
var temp2 = difference[i];
var PersonToRemove = db.Permissions.SingleOrDefault(s => s.EmployeeName == temp2 && s.StoreId == Persons.StoreId);
if (PersonToRemove.EmployeeName != null)
{
db.Entry(PersonToRemove).State = EntityState.Deleted; // do this instead
db.SaveChanges();
}
EF should then figure out which entity you wanted to delete
UPD
I am assuming you are using EF6 and DB-first approach. I am also assuming you've got your DB context class set up with default convention model builder. It seems EF's default object tracking based on Key will not work as your key is not unique (this is a bigger problem, but I understand you're already aware of that).
You might try circumvent that convention by adding custom model builder configuration like so:
class MyDbContext : DbContext {
public virtual DbSet<Permissions> Permissions {get;set;}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Permissions>().HasKey(p => new { p.EmployeeName, p.StoreId});
}
}
since you didn't share your DbContext definition this is just a snippet but hopefully gives you some ideas to explore.
this is the API reference: https://learn.microsoft.com/en-us/dotnet/api/system.data.entity.modelconfiguration.entitytypeconfiguration-1?view=entity-framework-6.2.0

ObjectContext Error with using .AddRange in EF 6

I'm trying to find some help on an error I'm getting when using the .AddRange in EF 6. I'm getting the following error.
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: AcceptChanges cannot continue because
the object's key values conflict with another object in the ObjectStateManager. Make sure that the key values are
unique before calling AcceptChanges.
As the error states, my records are actually getting added to the table so I don't know where fix the error.
Doing some research I found a bunch of posts where others say that it has to do with the .edmx file and a primary key on the table. Their suggestion is basically to add the PK and then rebuild the .edmx file. This doesn't fit my scenario for two reasons, one is that I'm using EF 6 with DataBase First so there isn't an .edmx file and second is that this is mapped to and Oracle 11 DB and so the identity is created with a trigger (which seems to work when I look at the added records).
Here is my code I'm using as well as the class for the entity.
using (APIcontext db = new APIcontext())
{
if (listLostTime.Count > 0)
{
db.GROUND_HOURS.AddRange(listLostTime);
db.SaveChanges();
}
}
And the entity class
[Table("GROUND_HOURS")]
public partial class GROUND_HOURS
{
[Key]
public decimal RID { get; set; }
[Required]
[StringLength(8)]
public string EMP_ID { get; set; }
[StringLength(2)]
public string COMPANY_CODE { get; set; }
public DateTime OCCURRENCE_DATE { get; set; }
[Required]
[StringLength(25)]
public string PAY_CODE { get; set; }
public decimal PAY_HOURS { get; set; }
public DateTime INSERT_DATE { get; set; }
}
I'm looking for any suggestions.
Decorate the RID property with the attribute DatabaseGenerated( DatabaseGeneratedOption.Identity )
The problem is that entity framework isn't updating the key value RID with the store generated value prior to accepting changes. In your case, with multiple GROUND_HOURS entities created, each will (presumably) have the default RID value of 0. When EF attempts to accept changes, it recognizes than more than one entity has the same key value and complains.
Thanks to #Moho who gave the ultimate fix. This is how I changed the primary key in my entity class to work and is what I used in my application.
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int RID { get; set; }
I was also able to fix it another way just to let others know. First off because this is and Oracle DB the RID (which is my Primary Key) was scaffold as a Decimal. That caused the RID to always be a 0 when I added my object to a list without specifically assigning it a value. To work around that I changed the RID property to an nullable INT and then when I created my list I set the RID=NULL.
[Key]
public int? RID { get; set; }
This is what I did when created my list.
foreach (var item in results)
{
GROUND_HOURS lostTime = new GROUND_HOURS();
lostTime.RID = null;
lostTime.EMP_ID = item.EmployeeId.ToString("D8");
lostTime.COMPANY_CODE = item.CompanyCode.Trim();
lostTime.OCCURRENCE_DATE = item.OccurrenceDate;
lostTime.PAY_CODE = item.PayCode.Trim();
lostTime.PAY_HOURS = item.Hours;
listLostTime.Add(lostTime);
}

Cannot insert the value NULL into column in ASP.NET MVC Entity Framework

When trying to use this code:
var model = new MasterEntities();
var customer = new Customers();
customer.Sessionid = 25641;
model.Customers.Add(customer);
model.SaveChanges();
I get:
{"Cannot insert the value NULL into column 'Sessionid', table
'master.dbo.Column'; column does not allow nulls. INSERT
fails.\r\nThe statement has been terminated."}
The column "Sessionid" is actually the primary key and is marked with [KEY] like this:
public class Customers
{
[Key]
public long Sessionid { get; set; }
public long? Pers { get; set; }
}
So according to this question, it seems as if when the property is marked with [KEY], EF ignores my own declaration of Sessionid since it expects the database to assign the value.
So how can I solve this? If I remove [KEY] I get the "entity type has no key defined" exception...
I solved it by adding [DatabaseGenerated(DatabaseGeneratedOption.None)] like this:
public class Customers
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public long Sessionid { get; set; }
public long? Pers { get; set; }
}
You can configure SQL to auto-generate (and auto-increment) the primary key for the table upon inserts. Then just remove the [Key] in C# and you don't need to set the ID in the application manually, the db will generate it for you.
I have encountered this problem multiple times while working with Microsoft SQL Server and I have followed the same way to fix it. To solve this problem, make sure Identity Specification is set to Yes. Here's how it looks like:
In this way the column number auto increments as a primary key normally would.
HOW?: right-click the table that contains the column, choose Design, select the primary key and in Column Properties window find Identity Specification and set it to Yes.

Categories

Resources