I am using EF core to store a local copy of a database. When a row is removed from the server database I need to remove that row from the local database using EF Core. I have run into an issue on an entity that uses HasConversion to convert a string property to upper. When EF core tries to delete one of these entities a DbUpdateConcurrencyException is thrown. I think I understand what is causing the issue but I dont know how to resolve it.
The entity in question is defined like this:
public class PartBin : BaseEpiView
{
public string PartNum { get; set; }
public string WarehouseCode { get; set; }
public string BinNum { get; set; }
public decimal OnhandQty { get; set; }
}
Now in the original database on the server the BinNum has values like "Elec101", "Elec102", "ELEC103", "elec104". Note the inconsistent capitalisation. To address this I have made use of HasConversion to convert the values to Upper.
modelBuilder.Entity<PartBin>().Property(p=> p.BinNum)
.HasConversion(v => v.ToUpper(), v => v.ToUpper());
This works and all the BinNum values are in Upper and displayed nicely to the user.
However the problem comes when one of these entries needs to be deleted. EF Core is throwing this error when SaveChanges() is called.
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: 'Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.'
I think this is caused by the HasConversion because in the database the BinNum is "Elec101" while on the entity it is "ELEC101" so EF Core thinks the data has changed and throws the exception. I need to tell EF core the data has not changed and it is ok to go ahead and delete the row...
What I have tried.
Reading all the docs and SO questions I can find
Remove the HasConversion which did resolve the delete issue but then all the BinNum values have inconsistent capitalistion in the local database.
Use the IsConcurrencyToken() to tell EF Core to look at the PartNum property when considering if an entity has changed. This didnt work.
modelBuilder.Entity<PartBin>().Property(p => p.PartNum).IsConcurrencyToken(true);
On the BaseEpiView class that PartBin inherits from there is a SysRevID. This is the SQL RowVersion and is incremented by the MS SQL server. If this value has not changed then the row has not changed.
public abstract class BaseEpiView : BaseObject, IEpiObject
{
public Byte[] SysRevID { get; set; }
public string Company { get; set; }
}
I tried setting that as the RowVersion in the model Builder.
modelBuilder.Entity<EpicorPartBin>().Property(p => p.SysRevID).IsRowVersion();
but still the DbUpdateConcurrencyException is thrown.
It would seem like using the IsRowVersion() is the correct way to address this issue but it is not working for some reason? Have I implemented it incorrectly?
Other Information:
EF Core 5.0.0-rc.2.20475.6 (I should update to a released version and check that....)
local database is SQLite
Server database is MS SQL Server
EF Core is hosted in a WPF .Net Core app
Related
I've been working on an application using ASP.NET Core using the Entity Framework Core connector for Cosmos DB. For the most part, it's been smooth sailing. I've reached a point where I'd like to add support for soft-deleting records from the database, using my application to query for a DeletedAt property to determine whether or not to include an entity in the results.
I'm using a base entity type to which I'm adding the aforementioned timestamp:
public abstract class Entity
{
[Key]
public long Id { get; internal set; }
public DateTimeOffset CreatedAt { get; set; }
public DateTimeOffset? UpdatedAt { get; set; }
public DateTimeOffset? DeletedAt { get; set; }
}
This updated code runs just fine. However, I do have several thousands of entities in my database that don't have the DeletedAt property defined.
I'm currently performing my queries from a generic repository type that does something along the lines of :
return await Queryable.Where(x => x.DeletedAt == null).Where(predicate).ToListAsync();
This works fine for new entities that do have a DeletedAt property defined, but excludes all of my older entities that don't have the property set. I'd expect EF to assume default values for properties that aren't defined, but it appears to ignore the old entities altogether.
Normally, using SQL Server, I'd just apply a migration and retrofit all of the older entities with a null DeletedAt timestamp, and all would be dandy. However, in Cosmos DB, I'm not sure how to handle this case. Do I have to go through all the older entities and retrofit them with the deletion timestamp, or is there another way to deal with changing entities and values missing?
Thanks!
In Cosmos there's a difference between null and no value (undefined). In SQL query text you would use the following to query on a property being defined (and additionally you can add a null check to the expression):
SELECT *
FROM c
WHERE NOT IS_DEFINED(c.example)
For the C# Cosmos SDK with linq you could use Microsoft.Azure.Cosmos.Linq and do:
var qry = container.GetItemLinqQueryable<Item>()
.Where(x => !x.Example.IsDefined())
.ToFeedIterator();
For EF this unfortunately doesn't seem to be possible as of this moment. Links:
Git issue
Current available functions
If you want to set it to null you would indeed need to iterate over all items and set it to make such queries possible using EF.
If you are using the .NET SDK for Cosmos DB, and thus using its deserialisation (which uses json.NET) then if your model type has a nullable property you don't need a value for that property set in the CosmosDB document.
It will remain a .NET instance with null values.
Thus optional properties in the CosmosDB document can be easily handled with nulls in your application code.
I set my serialisation options for Cosmos DB to not save null values to just avoid storing such properties all together.
I am wondering if it makes sense to create a migration for a scenario where there aren't necessarily model changes, but the enum property on a model has been changed. I'm using .NET 4.6.2 and Code-First Entity Framework
I have the following EF-tracked log model:
[Table("Logs")]
public class Log
{
public int Id { get; set; }
public DateTime Timestamp { get; set; }
public LogType Type { get; set; }
}
The LogType enum currently has around 40 values, of which, 13 have become obsolete or deprecated. I am going through the process of removing references to the obsolete/deprecated enum values.
As such, the values of the LogType enum are being changed. For example, LogType.ConnectionTimeout used to have the value 16, but now has the value 3.
In my database (MSSQL), the Type column is stored as an int, and I have written SQL that deletes all entries with an obsolete/deprecated enum value, and I have also written SQL that updates the other enum values to match what their new values are (e.g. changing 16 to 3 using my previous example of Type.ConnectionTimeout).
My question is this: Is it a good practice to put that SQL in a migration that is able to be Up()'d and Down()'d? My other question is, is that even possible? Or does there need to be actual model changes to create a migration? I'd like to be able to have this SQL tracked and stored in version control, as well as the ability to Up() and Down() it in the future if need be.
Thanks, and apologies in advance if this is a duplicate -- I wasn't able to find a similar question through my searches.
I have a question regarding the setup of foreign keys in entity framework 6. Our project stores data from a few other services (to have faster access to the data) and provides the users with charts and statistics depending on the stored data. For the storage of the data we´ve setup a cronjob which runs daily at about 3 AM.
Here are 2 example database models:
public class Project {
public string Id { get; set; }
public string Title { get; set; }
}
public class Issue {
public string Id { get; set; }
public string Title { get; set; }
[ForeignKey("Project")]
public string ProjectId { get; set; }
[ForeignKey("ProjectId")]
public Project Project { get; set; }
}
The problem now is for some issues we don´t save the project it depends on but we have to save the ProjectId (because at a later point it might be possible that the project exists in our database). So when I try to save this issues it tells me that I can´t save them because the project does not exist.
Is there any way I can tell entity framework that it doesn´t matter if the project exists or not? Currently I´ve just removed the ForeignKeys but this makes it very slow when I try to get the full list of issues with their projects.
Or is there any other way to read out all issues with their projects if there are no foreign keys? Currently I´m using a foreach loop to go threw each issue and then I search for the project but with more than 10.000 issues this get´s very slow.
The navigation property you've defined is requiring the data in the Project table in order to save an Issue. This isn't Entity Framework, this is a SQL Server foreign key constraint issue. Entity Framework is doing preliminary validation to not waste a connection that will ultimately fail. While you can turn off enforcing the foreign key constraint, there is not a good way to turn this validation off in Entity Framework
Keep in mind, having a foreign key does not mean it will help with your query's performance. It's simply a way to enforce referential integrity. I suspect that your real problem is the way you've written your query. Without seeing your query and metrics around "slow", it be hard to point you in the right direction.
Here is the super simple class I'm trying to create.
public class Company
{
public int ID { get; set; }
[Column(TypeName = "VARCHAR(254)")]
[Index]
public string Name { get; set; }
[Index]
public int stupidField { get; set; }
}
My goal was to force Name to be unique, so I added the decoration [Index(IsUnique = true)]. But no unique index was created, so I figured I'll first try to solve the simpler problem of creating any index. Because I read here that indices cannot be created for columns of type varchar(max), I limited the length of the Name field. Still no luck. I even tried a few different syntaxes for limiting the length of the field, but still no index.
To see if something other than string length was at play, I created the integer field stupidField, but I can't index that field either. So now I'm completely out of ideas as to what could be wrong. Please help me!
Check out this screenshot from MS SQL Server Management Studio that shows that my fields are being created but not the indices.
Note: I'm certain migrations are not the issue.
Some of the people I've read about on SO were updating their classes, but those changes were not reflected in the database because of problems with their migrations. That is not relevant here. I delete the database and recreate it every time I make a change. (I even make silly changes like renaming my fields, just to make sure that I can still affect the database.)
Turns out I'm actually using Entity Framework Core, not Entity Framework. In Entity Framework Core, indices cannot be created using attributes, although they can be created using fluent API. See Microsoft's documentation.
I've looked at lots of other questions on SO but can't get the answer. I have a column in a table called: Sex - Male
I would like to get my hands on whoever named it as it's giving me problems with EF. If I use this:
[Column("Sex - Male")]
public bool Sex { get;set; }
This gives me the error of being incompatible with the model as the field "Sex" could not be found. So I changed to this:
[Column("[Sex - Male]")]
public bool Sex { get;set; }
I then get the message Invalid Column Name [Sex - Male]. Does EF rename columns with spaces in some way as the field does exist and is not any kind of FK?
EDIT
I have found that doing this in the modelBuilder:
modelBuilder.Entity<Student>().Property(x => x.Sex).HasColumnName("Sex - Male");
Causes the same error to appear saying it's incompatible as there is no column called Sex with the same name! I've noticed it occurs on anything I use the Column data annotation for not just this field!
EDIT 2
I created a new application and used a Model Designer to see how it interpreted the column and showed it in the designer as "Sex___Male", however, changing the class to this even with []'s around it still gives me could not find column Sex___Male??
EDIT 3
It appears the error isn't quite what I thought, I found the mapping config works fine when I just use db.Students; and the column is there as expected.
It turns out the area going wrong is this line:
var students = (db as IObjectContextAdapter).ObjectContext.ExecuteStoreQuery<Student>(sql);
So it's clearly the ExecuteStoreQuery that I'm guessing won't use the same mapping configuration therefore sees the column as missing. Not sure why putting the Column annotation on the property in the class doesn't work though??
I have recreated your situation in a test configuration. I was able to succesfully insert and query data using the following configuration
SQL Server 2012
Visual Studio 2013
Entity Framework 6.0.1
If you are using an older version of Entity Framework I would consider updating; that's most likely the cause, however I'm not able to reproduce your environment so this answer is only a guess. I used this code:
Created a table:
create table MyTable2 (
[pk] int not null identity primary key,
[Sex - Male] bit not null);
Class:
public class MyTable2
{
public int pk { get; set; }
public bool Sex { get; set; }
}
Mapping configuration:
this.HasKey(t => t.pk);
this.Property(t => t.Sex).HasColumnName("Sex - Male");
It appears that Entity Framework itself had no issue mapping this column regarding it's normal use however the issue I had is where I was using the ExecuteStoreQuery method to map the model.
It turns out using this means anything you map it to has to have the same names regardless of any data annotations you add on for column (they appear to just get ignored). What I did instead was make a small class with just the fields I needed and changed the sql of the query to Select StudentID As ID, [Sex - Male] As Sex, ...other fields FROM ...etc i.e.
public class StudentReadOnly
{
public int ID { get; set; }
public bool Sex { get;set; }
... other properties
}
And then changed line to:
var students = (db as IObjectContextAdapter).ObjectContext.ExecuteStoreQuery<StudentReadOnly>(sql);
And had no problems. I also found that any properties you put in the class MUST exist in the sql query unlike a usual ef query.