Entity Framework to read a column but prevent it being updated - c#

Given a database table with a column that contains historic data but that is no longer populated, is there a way in Entity Framework to read the column but prevent it being updated when using the same model object?
For example I have an object
public class MyObject
{
public string CurrentDataColumnName { get; set; }
public string HistoricDataColumnName { get; set; }
}
From the documentation I don’t believe I can do either of the following, because this will stop EF reading the data as well as persisting it.
(1) Decorate the HistoricDataColumnName property with the following attribute
[NotMapped]
(2) Add the following to my EntityTypeConfiguration for MyObject
Ignore(x => x.HistoricDataColumnName)

You can mark the column as computed to prevent Entity Framework from updating / inserting into that column.
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public string HistoricDataColumnName { get; set; }
DatabaseGenerated
An important database features is the ability to have computed
properties. If you're mapping your Code First classes to tables that
contain computed columns, you don't want Entity Framework to try to
update those columns. But you do want EF to return those values from
the database after you've inserted or updated data. You can use the
DatabaseGenerated annotation to flag those properties in your class
along with the Computed enum. Other enums are None and Identity.

You can simply use IsModified to check whether a specific entity property was modified or not and by this way you can still Read,Insert and Delete data:
var item = context.MyObjects.Find(id);
item.CurrentDataColumnName = "ChangedCurrentDataColumnName";
item.HistoricDataColumnName = "ChangedHistoricDataColumnName";
context.Entry(item).Property(c => c.HistoricDataColumnName).IsModified = false;
context.SaveChanges();
By using IsModified = false you are excluding the HistoricDataColumnName property from updating, so the HistoricDataColumnName column will not be updated in the database but other properties will be updated.
Setting this value to false for a modified property will revert the change by setting the current value to the original value. If the result is that no properties of the entity are marked as modified, then the entity will be marked as Unchanged. Setting this value to false for properties of Added, Unchanged, or Deleted entities is a no-op.
Check the following answer as a supplementary explanation. It might be helpful also:
https://stackoverflow.com/a/13503683/2946329

Codewise you can set the setter simply to protected. EF useses reflection to materialize your model. I think the now hidden setter also shows to every other programmer, that the field should not be modified any longer.
Also add an [Obsolete]-attribute with further information, why the property can't be set from the public anymore.

Since you say 'at the EF level or lower' a possible solution is to use a trigger to either raise an error if an attempt is made to change the column, or allow the update but ignore the change on the column of interest.
Option 1 - raise an error
CREATE TRIGGER MyTable_UpdateTriggerPreventChange
ON dbo.Table1
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
if update(HistoricDataColumnName)
begin
raiserror (50001, 16, 10)
end
END
Option 2 - ignore the change
CREATE TRIGGER MyTable_UpdateTriggerIgnore
ON dbo.Table1
INSTEAD OF UPDATE
AS
BEGIN
SET NOCOUNT ON;
update dbo.Table1 set HistoricDataColumnName=inserted.HistoricDataColumnName
from inserted
where inserted.Id = dbo.Table1.Id
END
You could of course do something similar for inserts if required.
Alternatively to raiserror use 'throw'
ALTER TRIGGER MyTable_UpdateTriggerPreventChange
ON dbo.Table1
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
if update(HistoricDataColumnName)
begin
throw 50002, 'You can''t change the historic data', 1
end
END
either way you'll get an exception thrown. This is using LinqPad

For just on column this is overkill, but in general you can override SaveChanges in DbContext to have more control on the changes.
In your model:
public override int SaveChanges()
{
var modifiedEntries = base.ChangeTracker.Entries<MyObject>()
.Where(e => e.State == EntityState.Modified).ToList();
foreach (var entry in modifiedEntries)
{
// Overwriting with the same value doesn't count as change.
entry.CurrentValues["HistoricDataColumnName"] = entry.OriginalValues["HistoricDataColumnName"];
}
return base.SaveChanges();
}
But you could also undo all modifications by changing the state from modified to unchanged.
-- UPDATE --
There is one thing that worries me. As soon as a developer has the credentials to access the database you cannot prevent them from doing things you don't want. They could create their own model or query the database directly.
So I think the most important thing to do is to set the field to readonly in the database for the client. But you may not be able to lock one column.
Even if this is not an issue, I think (for design) it is better to move all historical data to other tables. Making it easy to grant readonly access only. You can map these tables 1:1. With Entity Framework you can still access the historical information quite easy.
But in that case you won't have the problem you have now and will give you other options to prevent others from changing the historical information.

internal access modifier
You could change the setter to internal
public class MyObject
{
public string CurrentDataColumnName { get; internal set; }
public string HistoricDataColumnName { get; internal set; }
}
This doesn't impose as much limitations as the other options, but depending on your requirements, this can be quite useful.
protected access modifier
This would probably be the most common usage of making a property in EF "read-only". Which essentially only allows the constructor to access the setter (and other methods within the class, and classes derived from the class).
public class MyObject
{
public string CurrentDataColumnName { get; protected set; }
public string HistoricDataColumnName { get; protected set; }
}
I think protected is what you're looking for.
protected internal access modifier
You can also combine the two like this, to make it protected or internal
public class MyObject
{
public string CurrentDataColumnName { get; protected internal set; }
public string HistoricDataColumnName { get; protected internal set; }
}
Access Modifier Refresher Course
A internal member is accessible only within the same assembly
A protected member is accessible within its class and by derived class instances.
A protected internal member can be accessed from the current assembly or from types that are derived from the containing class.

The question is about EF 6, but this is easily doable in EF Core with the Metadata.IsStoreGeneratedAlways property. Thanks to ajcvickers on the EF Core repo for the answer.
modelBuilder
.Entity<Foo>()
.Property(e => e.Bar)
.ValueGeneratedOnAddOrUpdate()
.Metadata.IsStoreGeneratedAlways = true;

Why do this in EF in the first place? Why not simply ensure that any login being used to access the database either has the rights for performing UPDATE/INSERT/DELETE revoked or even go to the extreme of setting the database to READ_ONLY in the Database options?
It seems to me that any attempt to prevent updates via EF is doomed as you can always circumvent that and, for example, just execute SQL code directly against the EF connection.

As for me, it's simple solution - make property setters as private:
public class MyObject
{
public string CurrentDataColumnName { get; private set; }
public string HistoricDataColumnName { get; private set; }
}
EF will materialize objects from database without any problem, but yout won't have any way to change value int these properties.

Related

EF: manual (non autogenerated) keys require ad hoc handling of new entities

In a MVVM application with EF Core as ORM I decided to model a table with a manually inserted, textual primary key.
This is because in this specific application I'd rather use meaningful keys instead of meaningless integer ids, at least for simple key-value tables like the table of the countries of the world.
I have something like:
Id | Description
-----|--------------------------
USA | United States of America
ITA | Italy
etc. etc.
So the entity is:
public class Country
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string Id { get; set; }
public string Description { get; set; }
}
Here is my viewmodel. It's little more than a container for the ObservableCollection of Countries. Actually it gets loaded from a repository. It's trivial and I inlcuded the entire code at the end. It's not really relevant and I could do with just the DbContext as well. But I wanted to show all the layers to see where the solution belongs to. Oh yes, then it contains the synchronizing code that actually offends EF Core.
public class CountriesViewModel
{
//CountryRepository normally would be injected
public CountryRepository CountryRepository { get; set; } = new CountryRepository(new AppDbContext());
public ObservableCollection<Country> Countries {get; set;}
public CountriesViewModel()
{
Countries = new ObservableCollection<Country>();
Countries.CollectionChanged += Countries_CollectionChanged;
}
private void Countries_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
foreach (Country c in e.NewItems)
{
CountryRepository.Add(c);
}
}
}
In my MainWindow I just have:
<Window.DataContext>
<local:CountriesViewModel/>
</Window.DataContext>
<DockPanel>
<DataGrid ItemsSource="{Binding Countries}"/>
</DockPanel>
Problem and question
Now this doesn't work. When we try to insert a new record, in this case I do it using the automatic feature of DataGrid I get a:
System.InvalidOperationException: 'Unable to track an entity of type 'Country'
because primary key property 'Id' is null.'
Each time i add a new record to the ObservableCollection I also try to add it back to the repository, that in turn adds it on the EF DbContext that doesn't accept entities with null key.
So what are my options here?
One is postponing the addition of the new record till the Id has been inserted. This is not trivial as the collection handling that I've shown, but this is not the problem. The worst is that this way I would have some record that are tracked by EF (the updated and the deleted and the new with pk assigned) and some that are tracked by the view model (the new ones with the key not yet assigned).
Another is using alternate keys; I would have an integer, autogenerated primary key and the ITA,USA etc code would be an alternate key that would be used also in relations. It's not so bad from as simplicity, but I'd like a application-only solution.
What I'm looking for
I'm looking for a neat solution here, a pattern to be used whenever this problem arises and that plays well in the context of a MVVM/EF application.
Of course I could also look in the direction of the view events, that is force the user to insert the key before of a certain event that triggers the insertion. I would consider it a second-class solution because it is sort of view dependent.
Remaining code
Just for completeness, in case that you want to run the code, here is the remaining code.
DbContext
(Configured for postgres)
public class AppDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql("Host=localhost;Database=WpfApp1;Username=postgres;Password=postgres");
}
public DbSet<Country> Countries { get;set; }
}
Repository
The reason why I implemented the repository for such a simple example is because I think that a possible solution may be to include the new-without-key records managment in the Repository instead of in the viewmodel. I still hope that someone comes out with a simpler solution.
public class CountryRepository
{
private AppDbContext AppDbContext { get; set; }
public CountryRepository(AppDbContext appDbContext) => AppDbContext = appDbContext;
public IEnumerable<Country> All() => AppDbContext.Countries.ToList();
public void Add(Country country) => AppDbContext.Add(country);
//ususally we don't have a save here, it's in a Unit of Work;
//for the example purpose it's ok
public int Save() => AppDbContext.SaveChanges();
}
Probably the cleanest way to address the aforementioned issue in EF Core is to utilize temporary value generation on add. In order to do that, you would need a custom ValueGenerator like this:
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.ValueGeneration;
public class TemporaryStringValueGenerator : ValueGenerator<string>
{
public override bool GeneratesTemporaryValues => true; // <-- essential
public override string Next(EntityEntry entry) => Guid.NewGuid().ToString();
}
and fluent configuration similar to this:
modelBuilder.Entity<Country>().Property(e => e.Id)
.HasValueGenerator<TemporaryStringValueGenerator>()
.ValueGeneratedOnAdd();
The potential drawbacks are:
In pre EF Core 3.0 the generated temporary value is set onto entity instance, thus would be visible in the UI. This has been fixed in EF Core 3.0, so now Temporary key values are no longer set onto entity instances
Even though the property looks empty (null) and is required (default for primary/alternate keys), if you don't provide explicit value, EF Core will try to issue INSERT command and read the "actual" value back from database similar to identity and other database generated values, which in this case will lead to non user friendly database generated runtime exception. But EF Core in general does not do validations, so this won't be so different - you have to add and validate property required rule in the corresponding layer.

EF 6 calls INSERT instead of UPDATE

This could be a duplicate question but a lot of searching for the words in the title only got me a lot of unrelated results.
I have an entity that's roughly set up like this:
public abstract class A
{
public GUID AId { get; set; }
public string SomeProperty { get; set; }
}
public class B : A
{
public string SomeOtherProperty { get; set; }
}
The context has public DbSet<B> BInstances { get; set; } for B objects. In OnModelCreating, the mapping has A set to ignored and B is mapped to a table called TableB.
The AId field is not auto-generated (not an identity field) but it's set to be primary key, both in the database and in the mapping. In the database, the field is defined as a non-null uniqueidentifier with no default.
At runtime, I'm loading an instance of B using its key (_token is just a CancellationToken):
var b = await (dbCtx.BInstances.FirstOrDefaultAsync(e => e.AId), _token));
Then, a property of b is set and I try to save it back to database:
b.SomeOtherProperty = "some new text";
await (dbCtx.SaveChangesAsync(_token));
At this point, I'm getting a Violation of PRIMARY KEY constraint error from the database, stating that the value of AId cannot be inserted because it'd be a duplicate. Of course, the ID is already in the database, I loaded the entity from there, using the ID. For some reason, EF generates an INSERT statement, not an UPDATE and I don't understand why.
When I check dbCtx.Entry(b).State, it's already set to EntityState.Modified. I'm at a loss - can someone point out what I'm doing wrong? I never had issues with updating entities before but I haven't used EF with GUID primary keys (usually I use long primary keys).
I'm using EF 6 and .NET Framework 4.7.1.
Thank you all for the suggestions - this turned out to be a mapping problem that I caused.
In my OnModelCreating() call, I called MapInheritedProperties() on a type that didn't inherit from a base class (other than object, of course) - this seems to have triggered a problem. Other entities that do share a base class worked fine with the mapping call.
I also called ToTable() directly against the entity class - this broke my table mapping for reasons I do not understand. Once I moved that call inside Map(), it started working as expected.
So I went from this:
entity.ToTable("tablename");
to this:
entity.Map(m => m.ToTable("tablename"));
to solve the problem.
Hopefully this will be useful for future readers.
try this
b.SomeOtherProperty = "some new text";
dbCtx.BInstances.AddOrUpdate(b);
await (dbCtx.SaveChangesAsync(_token));
AddorUpdate will update your b instance if it is already added.

Can Entity Framework automatically extend models with datetime fields to keep the changing date

Can you tell Entity Framework to add an extra field for each field of a certain type? For example: Is it possible to generate a ChangedAt datetime field for each boolean field defined in the model, so this
public bool Confirmed { get; set; }
could result in a table with an additional field ConfirmedChangedAt where the value is updated each time the boolean value is changed.
Usually behavior like this should be implemented directly into your business logic and not automatically into the data layer. So I suggest to write something like this:
// entity
public class Order
{
public bool Confirmed { get; set; }
public DateTime? ConfirmedAt { get; set; }
}
// business logic
public class OrderManager
{
.................
public void Confirm( Order order )
{
// changing of entity status
order.Confirmed = true;
order.ConfirmedAt = DateTime.Now;
// storing new entity status
_orderRepository.Update( order );
................
}
}
I think if i understand you correctly, You are expecting the Entity Framework to be able to add columns to the database automatically so that you don't have to add them manually, Well you have 2 cases:
if you are using the database first approach you could achieve this
by using a query that's specific to your needs to add these columns
for you based on the conditions you have.
If you are using the code first approach and you have an existing database you may reverse engineer the database using the Entity Framework Power Tools and you could customize the T4 Templates to generate the entities with the extra properties that you need.
Plain answer no.
But it's depend on way how you interact with EF (code first, model first,database first).
If you using EF 6 and code first approach you can use idea of base Entity class
public class BaseEntity
{
public DateTime ChangedAt {get;set;}
}
public class ConcreteEntity : BaseEntity
{
public string Name {get;set;}
}
Now ConcreteEntity has ChangedAt by inheritance.
If this solution not for you, please explain question with more details.

Prevent EF 5 from generating a property

I'm using EF5 database first with partial classes. There's a property in my partial class which contains n object which is stored as a column in my database containing XML data. I want to handle the serialization/deserialization of this object when the EF tries to read/write it with a custom getter/setter.
Is it possible to expose the column in my partial class and map it using the EF, without auto-generating a property for it?
ie:
public SomeObject BigComplexObject { get; set; } // forms etc in my app use this
public string BigComplexObjectString // when the EF tries to read/write the column, my custom getter/setter kicks in
{
get { return this.BigComplexObject.ToXmlString(); }
set { this.BigComplexObject = new BigComplexObject(value); }
}
At present, the EF is auto-generating a member for the column so I'm left with two.
Try to change the logic. Leave EF generated property that will be populated with XML string from the database:
public string BigComplexObjectString { get; set; }
Then do the following:
[NotMapped]
public SomeObject BigComplexObject
{
get { return new SomeObject(this.BigComplexObjectString); }
set { this.BigComplexObjectString = value.ToXmlString(); }
}
Don't forget to add [NotMapped] to instruct EF to ignore this property.
Well, we use a little trick for a quite similar case...
We use the property panel (in the edmx file) of our... properties and add something in the "documentation" (summary or long description) line (probably not the best place, but anyway). This can be access by your T4 file.
So you could write something like "useXml" in the property panel, then modify your tt to generate the desired code when (example to get the info in the .tt file)
if (edmProperty.Documentation != null && edmProperty.Documentation.Summary = "useXml")
//generate something special
It would be great to have a better place for "cusom infos" in the edmx, but we didn't find anything better for instant.

Entity Framework 6 Code First default datetime value on insert

When I'm saving changes to the database, I'm running into the following exception:
Cannot insert the value NULL into column 'Registered', table
'EIT.Enterprise.KMS.dbo.LicenseEntry'; column does not allow nulls.
INSERT fails. The statement has been terminated.
The related code first model property looks like this:
[DatabaseGenerated(DatabaseGeneratedOption.Identity), DataMember]
public DateTime Registered { get; private set; }
... and here's why I'm confused: As far as I know, by providing the annotation [DatabaseGenerated(DatabaseGeneratedOption.Identity) I'm ordering Entity Framework to auto-generate the field (in this case: Only once at creation time.)
So I'm expecting to have a non-nullable (required) field, without the possibility to alter the field manually where EF is taking care of.
What am I doing wrong?
Notes:
I don't want to use Fluent-API, as I want to use POCOs.
The property defaultValueSql is also not an option, because I need to rely database independed for this project (e.g. for GETDATE()).
I'm using Entity Framework 6 alpha 3, Code First.
Try this:
[DatabaseGenerated(DatabaseGeneratedOption.Identity), DataMember]
public DateTime? Registered { get; private set; }
The question mark makes the property nullable
There is no default DateTime. Only a min value.
You could potentially just set the datetime on that entity before calling dbContext.SaveChanges.
See this for a solution.
This technique behaves like a readonly field that is persisted to the database. Once the value is set it cannot (easily) be changed by using code. (Of course, you change the setter to public or internal if needed.)
When you create a new instance of a class that uses this code Registered will not initially be set. The first time the value of Registered is requested it will see that it has not been assigned one and then default to DateTime.Now. You can change this to DateTime.UTCNow if needed.
When fetching one or more entities from the database, Entity Framework will set the value of Registered, including private setters like this.
private DateTime? registered;
[Required]
public DateTime Registered
{
get
{
if (registered == null)
{
registered = DateTime.Now;
}
return registered.Value;
}
private set { registered = value; }
}
What I did was I set value as optional and nullable. I used Data Annotation, but its also possible to use IsOptional in Fluent Api:
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime? UpdatedAt { get; set; }
Then I created another Sql migration, to alter value to default:
public partial class AlterTableUpdatedAtDefault : DbMigration
{
public override void Up()
{
Sql(#"ALTER TABLE dbo.[Table]
ADD CONSTRAINT DF_Table_UpdatedAt
DEFAULT getdate() FOR UpdatedAt");
}
public override void Down()
{
Sql(#"ALTER TABLE dbo.[Table]
drop CONSTRAINT DF_Table_UpdatedAt");
}
}

Categories

Resources