Dapper ignore property runtime - c#

Is there a way to ignore property from being mapped in runtime. Because I don't know if database has specific column and I have to check it before doing insert. If database doesn't have column then I want to ignore this one specific property.
UPDATE:
Here's my insert code
public static void Insert(string connectionString, T entity)
{
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
connection.Insert(entity);
}
}

That Insert method is part of Dapper.Contrib, not Dapper itself. As the Readme for that library explains, you can use the [Write(false)] attribute to specify that a property isn't writeable, eg :
public class MyClass
{
public int ID {get;set;}
public DateTime Created{get;set;}
[Write(false)]
public DateTime CreatedDate =>Created.Date;
}
The source code shows that Dapper.Contrib simply ignores properties that aren't writable :
var properties = type.GetProperties().Where(IsWriteable).ToArray();
Dapper is a microORM, it doesn't offer the mapping features found in full ORMs like EF or NHibernate. Dapper.Contrib adds some helper methods and very basic mapping through 5 atrributes:
[Table("Tablename")] to specify the table name
[Key] to mark an auto-generated key field
[ExplicitKey] to mark a field that isn't generated automatically
[Write(true/false)] to mark (non)writable properties
[Computed] to mark calculated properties.
There's no way to specify a column name for example

Related

Dapper.SimpleCRUD Insert / Update / Get fails with message "Entity must have at least one [Key] property"

I'm a new baby in Dapper. Trying to incorporate CRUD operations with Dapper and Dapper.SimpleCRUD lib. Here is the sample code...
My Data Model Looks like
Class Product
{
public string prodId {get;set;}
public string prodName {get;set;}
public string Location {get;set;}
}
Dapper Implementation - Insert
public void Insert(Product item)
{
using(var con = GetConnection())
{
con.Insert(item);
}
}
Since the ProdId in the Db is an Identity Column it fails. How does it indicate ProdId is an Identity Column in DB?
Dapper Implementation - Get
public IEnumerable<Product> GetAll()
{
IEnumerable<Product> item = null;
using (var con = GetConnection())
{
item = con.GetList<Product>();
}
return item;
}
It gives an exception:
"Entity must have at least one [Key] property"!
This is happening since you are using a Dapper Extension, which has implemented the Insert CRUD extension method. Ideally this can be achieved using simple
con.Execute in the Dapper, but since you want to pass an object and create an insert query automatically by the extension, you need to help it understand, which is the Primary Key for the given product entity, following modification shall help:
[Key]
public string prodId {get;set;}
where Key attribute shall be either implemented in Dapper Extension or the Component Model.
Alternatively you may rename prodId to Id, which will automatically make it the key. Also check the following link, where you can create a separate mapper for the entity, thus defining the key, whatever works in your case
Connecting to SQL Server 2016, I had this error with both Dapper.Contrib & Dapper.SimpleCRUD when I forgot to attach the Primary Key to the Id column of the table.
Primary Key added to the table, project rebuilt & published to clear cache and all is good with both [Key] & [ExplicitKey] ( the latter in DapperContrib).

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.

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.

Entity Framework - default values doesn't set in sql server table

SQL server 2005 database table has a column 'createdon' for which default value set to getdate(). I am trying to add a record using entity framework. 'createdon' column is not getting updated.
Did I miss any property in Entity framework, please suggest.
This is one of the few issues that are problematic with Entity Framework. Say you have a class that looks like this:
public class MyEntity
{
// Id is a PK on the table with Auto-Increment
public int Id { get; set; }
// CreatedOn is a datetime, with a default value
public DateTime CreatedOn { get; set; }
}
Now, you want to insert a new element:
using(var context = new YourContext())
{
context.MyEntities.Add(new MyEntity())
}
Entity Framework knows how to handle an auto-increment primary key because of the definition in the EDMX. It will not try to insert a value for the Id property. However, as far as Entity Framework is concerned, CreatedOn has a value: the default DateTime. Because Entity Framework cannot say "well, it has a value but I should ignore it", it will actively insert the record with the CreatedOn property value, bypassing the default value on your column definition on your table.
There is no easy way to do this. You can either actively set the CreatedOn property to DateTime.Now when you insert that item. Or you can create an interface and an extension method pair:
public interface ICreatedOn
{
public DateTime CreatedOn { get; set; }
}
public partial class MyEntity : ICreatedOn
{
}
public static TEntity AsNew<TEntity>(this TEntity entity) where TEntity : ICreatedOn
{
if(entity != null)
entity.CreatedOn = DateTime.Now;
return entity;
}
using(var context = new YourContext())
{
context.MyEntities.Add(new MyEntity().AsNew())
}
Edit: To expand on this point, the reason why this is an unresolvable issue is because of the meaning behind an autoincrement field and a field with a default value constraint. An auto-increment field should, by definition, always be handle by the server, using a seed and all that jazz. You cannot specify a value for an auto-increment field on an insert unless you have used SET IDENTITY INSERT ON. A default value, however, is just a hint that say "if I don't specify any value, use this". Because value types in .NET cannot be null, there will always be a value and Entity Framework cannot infer that the default value for that field, at that time, means that you want it to be defaulted on the SQL server.
Next to using the designer and some more nifty stuff shown already, you can also mark the columns as being calculated by simply setting the DatabaseGenerated attribute on the field:
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime CreatedOn { get; set; }
You can set StoreGeneratedPattern to Computed (as Malcolm suggested) in the GUI of the entity data model as well.
Open your .edmx file in Visual Studio
Open the properties of the field (click on the field -> hit
F4 or right click->properties)
Set StoreGeneratedPattern to Computed in the properties window
as shown below:
I've got around this issue by telling EF that the column is 'computed', and should therefore be left alone for inserts.
If you look in the configuration for the generated entity
namespace Data.Context
{
// Table
internal partial class MyTableConfiguration : EntityTypeConfiguration<MyTable>
{
public MyTableConfiguration(string schema = "dbo")
{
ToTable(schema + ".MyTable");
HasKey(x => x.Id);
Property(x => x.ColumnName).HasColumnName("ColumnName").IsOptional().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
....

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