Entity framework map placeholder value to null - c#

In a legacy database, NULL values are sometimes stored as the string NULL and/or "x".
How can I tell EF that I want it to automatically convert those "placeholder values" to NULL?
Update:
I want to avoid ambiguity / accidental complexity in my model by having the same property twice. (If people can get/set LegacyColumn, they will and abstraction is broken)

You can create a new property based on the existing property that is not mapped to the database and that performs the desired conversion. Assuming the column is named Foo you can use this code:
[Column("Foo")]
public String FooWithWeirdNullValues { get; set; }
[NotMapped]
public String Foo {
get {
return FooWithWeirdNullValues == "NULL" || FooWithWeirdNullValues == "x"
? null : FooWithWeirdNullValues;
}
set { FooWithWeirdNullValues = value; }
}
In your code you then use the unmapped property to access the value of the Foo column.
You might want to pick another name for FooWithWeirdNullValues e.g. LegacyFoo or whatever. You can pick any name as long as you use the Column attribute to map it to the correct column in the database.
If desired you can make FooWithWeirdNullValues private to not pollute your model. This requires a few changes to the DbContext as described in an answer on Stack Overflow on how to map private properties using code first.

Related

Entity Framework to read a column but prevent it being updated

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.

Defining a consistent projection in a model with EF6

Background
We have a table, let's call it Files.
We have certain attributes on the row, such as Name, CreatedDate, etc.
We have a blob column with the contents of the file, FileBytes.
So our model looks similar to:
public class FileEntity
{
public string Name { get; set; }
public DateTime CreatedDate { get; set; }
public byte[] FileBytes { get; set; }
// many other fields, most of which we'd like to use
}
Goal
On certain queries, we only care about whether FileBytes is null, not anything about the bytes themselves.
We'd like to be able to query and have a field in our model class populated, say, FileHasBytes that is a bool.
We'd like this field to exist only in our class, so that we can refer to it in the webapp as part of the model.
We'd like to be able to query for this bool value without pulling the full bytes from the field.
Question
How can I, using EF6, define a field on my model class that will be consistently projected, based on another field in the table, without pulling the full contents of that field?
Considered Options / Workarounds
Computed column: we were hoping to avoid this because it seems unnecessary
View: We'd also like to avoid this because it seems unnecessary to go to this for a single column
Projection to a new object: This is doable, but we'd like to be able to map directly without selecting a new object each time, including all of the fields that go with it.
With the current version of EF6 you can't do exactly what you are after.
There are other alternatives, but with all of them you'd have to make compromises on the goals stated above. Such as either using a new projected type with the computed property, or not querying on the computed value and be explicit on the condition instead, etc.
However, using something like DelegateDecompiler it might be possible to have the model and query just as you are expecting.
Something along the lines of:
[Computed]
public bool HasFileBytes
{
get { return FileBytes != null; }
}
And in your query you'd use the .Decompile() call to get that translated:
var query = ctx.Files
.Where(x => x.HasFileBytes)
.Decompile();
Using Linq.Translations could be another similar alternative.
source: http://www.daveaglick.com/posts/computed-properties-and-entity-framework
This is not ideal but I think you can add a static property that returns QueryExpression like
public static Expression<Func<FileEntity,bool>> FileHasBytes
{
get { return ((c)=> c.FileBytes != null && SqlFunctions.DataLength(c.FileBytes)>0)
}
I have not tried this code, so take this with grain of salt, so try and test it thoroughly.
I have used some thing like this using Dynamic.linq some time ago, but not tried it lately
dbContext.FileEntities.Where("FileHasBytes == true"),

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 Code First: Computed property of type Type causes ArgumentNullException on Update-Database

As far as I understood, property of type Type cannot be directly stored in a database. Thus, I decided to create a wrapper - I want Entity Framework to store type name and create a property to return desired type:
[Table("DescriptiveCategory")]
public class DescriptiveCategory : Category {
public string RelatedTypeName { get; set; }
public Type RelatedType {
get {
return Type.GetType(RelatedTypeName);
}
set {
RelatedTypeName = value.FullName;
}
}
}
This, unfortunately, does not work: if I try to update database, I get
Value cannot be null.
Parameter name: extent
If I comment RelatedType property, everything is fine. But I don't want Entity Framework to anything with this property - it serves only my needs. I could probably create get and set methods instead but I don't understand why this approach (with computed properties) doesn't work.
Use NotMappedAttribute:
Denotes that a property or class should be excluded from database mapping.

How do I override the get; set; properties of a field?

I'm working with a database that due to reason out of my control - I cannot modify the schema.
This database has a field "CertificateId" that is non-nullable, however, the field is still considered to be optional. When I load this field into my model (I'm using the DB first approach), it is of course tagged as being non-nullable, as you can see in the designer.cs
[EdmScalarPropertyAttribute(EntityKeyProperty = false, IsNullable = false)]
[DataMemberAttribute()]
public global::System.String CertificateId
{
get
{
return _CertificateId;
}
set
{
OnCertificateIdChanging(value);
ReportPropertyChanging("CertificateId");
_CertificateId = StructuralObject.SetValidValue(value, false);
ReportPropertyChanged("CertificateId");
OnCertificateIdChanged();
}
}
Since this field is optional, it gets passed null sometimes, which obviously fails validation.
Is there any way of overloading these properties that are autogenerated? I would like to be able to check if the value passed into the set property is null, and if it is, set it to an empty string before it goes into validation.
Or, is it possible to override the metadata for this property and have IsNullable set to true?
First of all if the field is considered as not nullable you mustn't set its value to null. Once you manually assign null in your code it's your bug.
Another problem which have to be solved is default value (null) if you don't assign the value. This is well discussed in this question - I like the way with initializing the field with constructor.
If for any reason previous two options are not what you want you have a last choice of modifying the value in overriden SaveChanges. Something like:
public override int SaveChanges(SaveOptions options)
{
var data = context.ObjectStateManager
.GetObjectStateEntries(EntityState.Added | EntityState.Modified)
.Where(e => !e.IsRelationship)
.Select(e => e.Entity)
.OfType(MyEntity);
foreach(var entity in data)
{
if (entity.CertificateId == null)
{
entity.CertificateId = String.Empty;
}
}
return base.SaveChanges(options);
}
If You created your model classes with a designer, then you can manually set this property to be nullable in your model.
If your field is optional, why not make the database column nullable, instead of putting in empty string?
You could use poco's, or you could update the existing entity framework template file (T4 templates), so that you can add your null check within the template, and when the entity framework classes are generated is uses your template. I'll try and find you some links I've used in the past.
Here's information on using POCO's
http://msdn.microsoft.com/en-us/library/dd456853.aspx
Another way to handle this is to create a stored procedure, add that to your model, and handle your logic to make nulls empty strings within the sp.
Thanks guys, I simply used the constructor in a partial class to set my default:
public partial class Emp_Certificate
{
//Constructor
public Emp_Certificate()
{
this.CertificateID = "";
}
}

Categories

Resources