EntityFramework DatabaseGeneratedOption.Identity accepts and saves data instead of generating new one - entity-framework

Assuming this test model:
public class TestEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
}
When I generate a new instance of it, Id is 00000000-0000-0000-0000-000000000000.
Saving such an instance in the database as a new row, results in a Guid being generated (which is different from the empty one).
However, if I provide a valid Guid in TestEntity.Id, the new row is created with the provided Guid instead of a newly computed one.
I would like this behavior to exists only when editing a row, not when creating it. This is to ensure a database-layer protection from attacks where a user normally shouldn't get to choose which data to input.
Off course this protection is present in other layers, but I want it in the database too. Is this possible? How can I tell EF to ignore model data when creating a new row?
DatabaseGeneratedOption.Computed descriptions says
the database generates a value when a row is inserted or updated
So clearely that's not an option. I don't want to change Id when updating a row. I only want to be sure no one can create a row and choose the Id.

I'd try to keep things simple. Make your set method protected, then you have two ways to generate Ids, You can generate it by yourself inside a constructor:
public class TestEntity
{
// no need to decorate with `DatabasGenerated`, since it won't be generated by database...
[Key]
public Guid Id { get; protected set; }
public string Name { get; set; }
public TestEntity()
{
this.Id = Guid.NewGuid();
}
}
...or you can let the database generate it for you. At least for SQL Server, it will be able to generate for int and Guid as well:
public class TestEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid Id { get; protected set; }
public string Name { get; set; }
// no need to generate a Guid by yourself....
}
This will avoid people from setting a value to Id outside the class (therefore no one can choose a Guid for new rows, or modify from existing ones).
Of course, your team could use reflection to by-pass class definitions, but if that's the case, you need to have a talk with your team.
If you still want to make sure they won't cheat, then you'd have to do check before saving changes to database, maybe overriding SaveChanges() in your DbContext.
As a side note, for both int and Guid, values are not generated by Entity Framework. Decorating the property with [DatabaseGenerated(DatabaseGeneratedOption.Identity)] will tell Entity Framework to generate a column with a default value coming from the own database provider.

Related

add-migration queries the database for the columns I'm trying to add, fails with "Error: Invalid column name 'newColumn1', 'newColumn2', 'newColumn3'"

I'm trying to add three columns to an existing table via code first migrations with EF Core (package version 3.1.8). When I run add-migration <name> -c <context> -o <output folder>, it's throwing this error (along with a massive stack trace...):
An error occurred while accessing the Microsoft.Extensions.Hosting services. Continuing without the
application service provider. Error: Invalid column name 'NewColumn1'.
Invalid column name 'NewColumn2'.
Invalid column name 'NewColumn3'.
Unable to create an object of type 'MyDbContext'. For the different patterns supported at
design time, see https://go.microsoft.com/fwlink/?linkid=851728
Really baffled by this. This is the fourth migration I've added today, none of the previous ones had this issue.
This migration should add the three columns, data for the predefined rows for these columns, and add a default value constraint to newColumn1. The column data types:
newColumn1: bit, defaults to 0
newColumn2: nvarchar(50)
newColumn3: nvarchar(50)
My entity before adding my trouble migration:
public class MyEntity
{
[Key]
public int Id { get; set; }
[MaxLength(50), Required]
public string AttributeName { get; set; }
[Required]
public bool Required { get; set; }
}
This entity changed to the following prior to attempting to add this migration:
public class MyEntity
{
[Key]
public int Id { get; set; }
[MaxLength(50), Required]
public string AttributeName { get; set; }
[Required]
public bool Required { get; set; }
public bool NewColumn1{ get; set; }
[MaxLength(50)]
public string NewColumn2 { get; set; }
[MaxLength(50)]
public string NewColumn3 { get; set; }
}
In MyDbContext.OnModelCreating, I have the following new code:
builder.Entity<MyEntity>().Property(x => x.NewColumn1).HasDefaultValue(false);
The IEntityTypeConfiguration<MyEntity> has also, as previously mentioned, been updated to have data for all new columns for the predefined rows. No rows exist in the database besides the predefined rows.
I have a theory as to what's going on. I think add-migration requires an instance of MyDbContext, and when it gets instantiated it verifies that the database looks the way it expects. The context expects the table represented by MyEntity to have the three new columns that are defined in the entity, but they don't exist in the database. What I'm curious of is why this just started now? This is my fourth migration of the day, my other migrations added tables, columns, data....why would this just now start becoming an issue?
The msft documentation link in the error makes me think I need to implement IDesignTimeDbContextFactory<MyDbContext>, configure it in such a way that it makes it not choke. But looking at the behavior of DbContextOptionsBuilder, it doesn't look like any of the provided options will allow me to bypass the behavior I'm getting.
The database has been updated with all previous migrations, and I checked the DbContextModelSnapshot file...the new columns aren't anywhere in there (as expected). The database I'm targeting is a local SQL Server database.

Can I change Data Annottion at Runtime based on the type of storedprocedure I am calling

Hi my Entity is ParameterDetail as follows:
public class ParameterDetail
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
[NotMapped]
public string Description { get; set; }
//..other columns removed for brevity
public int LookupValueId { get; set; }
}
I call my stored procedure and load the results as follows...
List<ParameterDetail> paramDetails = this.ParameterDetails.FromSqlRaw("EXEC dbo.GE_GetStartParameter #GuidelineName={0}", guidelineName).ToList();
Now this all is working fine but now I have to call a slightly different procedure which does not have LookupValueId..
List<ParameterDetail> paramDetails =this.ParameterDetails.FromSqlRaw("EXEC dbo.GetParameterDetails #ParameterId={0}", nextParam).ToList();
I don't want to add another EntityModel just for this one column....
Can i use Mapped property (Data Annotation) at runtime somehow? Or could there be any other solution?
Can i use Mapped property (Data Annotation) at runtime somehow? Or could there be any other solution?
Not if you want EF to perform the mapping, at least not a good one. You can't change the attributes at runtime, but if you use Fluent configuration you could have two different DbContext subtypes that configure the same entity class differently. But that's not a very elegant solution.
You can always just execute the stored procedure with ADO.NET and map the data however you want.

EntityFramework Code First automatically setting the value of a public property

I am building a ASP.NET MVC 4 app that will allow users to upload a lot of images to the server. With each image certain entities will be associated, like the Product entity will be associated with the product images and the Category entity with the category images.
I am making a table in my schema to keep a record of all the uploaded files with a string 'Ref' which I will parse whenever the user wants to do an operation with the file, like deleting the file. That way when the user wants to delete the file I will simple mark the file in the schema for deletion and once a day I can 'sanitize' the server's uploaded files by deleting all the marked files in bulk.
For example a file image.jpg is uploaded as a product image, i will have in the ref tag image productID 4 which indicates this is an image for a product with productID 4.
Is this the right way to implement handling of uploaded files or is there a better way to do it? Is this the right way to 'reference' the other entities associated with this file?
Here is my codefirst entity :
public class UploadedFile
{
[Key]
public int ID { get; set; }
public string RelativePath { get; set; }
public string AbsolutePath { get; set; }
public string Ref { get; set; }
public DateTime UploadedOn { get; set; }
public bool IsMarkedForDeletion { get; set; }
public bool IsImage { get; set; }
}
Is there a way to declare certain public properties to be automagically set when other properties are set? For example I would like to add ImageWidth & ImageHeight property which are set automatically if the uploaded file type is an image (i.e. whenever I set IsImage = true)
It is fine to use a Ref column, however it would be even more beneficial to have an additional column specifying the type of the reference, in your case it will be a product, but you might want to use the same table for references to different types of objects.
The logic of setting related properties shouldn't be part of the Model but rather the logic of the service that sets the IsImage property to true. So simply set other related properties there.

An alternative way of implemening navigation properties in Entity Framework

The official approach to defining navigation properties for complex entities is:
public class SuperEntity
{
public int Id { get; set; }
//Other properties
}
public class LowerEntity
{
public int Id { get; set; }
public int SuperEntityId { get; set; }
public virtual SuperEntity SuperEntity { get; set; }
//Other properties
}
The main thing here is that a class that references (allows navigation to linked super entity) has both public SuperEntity SuperEntity { get; set; } property, as well as it's Id in public int SuperEntityId { get; set; }.
I have gone a few days into my entities design ommiting the public int SuperEntityId { get; set; } property in the "lower entities". So I am navigating only by virtual SuperEntity property. And everything works fine! But I had people on SO telling me that it creates an excessive tables in the DB. I've checked, and that is not true. When I use my approach, the DB tables has the SuperEntityId column and just populates it with the referenced entity Id automatically. What's the point in this public int SuperEntityId { get; set; } field then?
Or, perhaps, what I am doing became available in a "fresh" versions of EF like 4.3?
The point of SuperEntityId is that it is sometimes easier to use a foreign key property in apps where your context isn't alive the entire time, e.g. a webapp.
In such a situation, it's a lot easier to just use a foreign key property, than to try to attach object B to object A.
As far as I know, with nav properties, EF uses an object to track the relation between 2 objects. So if you want to couple object B to object A, in a disconnected app, it's not enough to just set the property on object A, you also have to fiddle with the entry of object A in the changetracker to register the relation between B and A.
Setting a foreign key property is the equivalent of this fiddling.
When we were just beginning with EF and didn't know about all of this, every time we wanted to connect 2 objects, e.g. B to A, and B already existed in the DB, the context thought that B was a new object instead of an existing one, and duplicated the record in the DB.
It won't create excessive tables, but it will probably generate extra, or longer, queries on that database. But that depends on how you're using these entities.

How can I have Entity Framework return related objects with some defaults?

Say I have Project and Task EF Code first classes
public class Project
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Task> Tasks { get; set; }
}
public class Task
{
public int ID { get; set; }
public string Name { get; set; }
public int ProjectId { get; set; }
public bool IsDeleted {get; set;}
public virtual Project Project { get; set; }
}
Say I have
public void SomeAction()
{
Project p= repository.GetById(1);
var tasks = p.Tasks;
//var tasks = p.Tasks.Where(t=>t.IsDeleted==false);
}
I would like that my Tasks property on the Project class will always perform that filter on IsDeleted and just return that subset ... to avoid having to write that condition all over the place...
Any recommendations?
Edit:
Im using EF Code First
Add a discriminator to your model in the OnModelCreating method
modelBuilder.Entity<TEntity>().Map(m => m.Requires("IsDeleted").HasValue(false));
Caveats
You can no longer load deleted items (unless you map IsDeleted true to another entity, then you may lose your automatic filtering)
The poco class cannot have the IsDeleted property (discriminators cannot be mapped)
because the IsDeleted cannot be mapped you need to run raw SQL to delete the entity in the first place.
EF Code first = NO WAY. Just one from long list of features which is available in EDMX and it is completely missing in code first. Mapped condition from EDMX does this but it is still problematic because it is hardcoded and cannot be changed (= you will never be able to load deleted entities even if you want to unless you use another EDMX). The solution would be implementation of global filters in EF but EF doesn't have anything like that despite the fact that old Linq-to-entities have them at least for relations (DataLoadOptions.AssociateWith).
This is much more painful in relations where you cannot use eager or lazy loading without loading deleted entities to your application as well and do filtering in your application's memory.
In the Model Designer, select your Task entity, and bring up the Mapping Details window. This should show you the database table your entity is mapped to, and all the columns. Just under where it says "Maps to [YourTable]" you should see an option <Add a Condition>. This should let you set a condition like what you're looking for.