Entity Framework MapToStoredProcedures Update and update not every fields of an object - entity-framework

I have a BaseEntity:
public class BaseEntity : IBaseEntity
{
[ScaffoldColumn(false)]
public string ID { get; set; }
[ScaffoldColumn(false)]
public DateTime CreatedOn { get; set; }
[ScaffoldColumn(false)]
public bool Deleted { get; set; }
}
And a person entity:
public class People : BaseEntity
{ //Fields }
And I want to do Update, Insert, Delete with a stored procedure, so in my context do this:
modelBuilder.Entity<People>()
.MapToStoredProcedures();
For don't update "CreatedOn" field, I override the SaveChanges as:
public override int SaveChanges()
{
var modifiedEntries = ChangeTracker.Entries()
.Where(x => x.Entity is IBaseEntity
&& (x.State == System.Data.Entity.EntityState.Added || x.State == System.Data.Entity.EntityState.Modified));
foreach (var entry in modifiedEntries)
{
IBaseEntity entity = entry.Entity as IBaseEntity;
if (entity != null)
{
DateTime now = DateTime.Now;
if (entry.State == System.Data.Entity.EntityState.Added)
{
entity.CreatedOn = now;
}
else
{
base.Entry(entity).Property(x => x.CreatedOn).IsModified = false;
}
}
}
return base.SaveChanges();
}
But when do update, stored procedure will be run and set default value for #createdOn parameter and return exception.
How can I use MapToStoredProcedures and update not every column?

Can you just let the database do the work of setting the value for you?
public class BaseEntity : IBaseEntity
{
[ScaffoldColumn(false)]
public string ID { get; set; }
[ScaffoldColumn(false)]
[DatabaseGeneratedOption(DatabaseGeneratedOption.Computed)]
public DateTime CreatedOn { get; set; }
[ScaffoldColumn(false)]
public bool Deleted { get; set; }
}
And then your UPDATE procedure shouldn't even pass the parameter at all. Inside the UPDATE stored proc, set the value for CreatedOn to GETDATE(), GETUTCDATE(), or whatever sets a date value in your database if you're not using SQL Server.
Idea here is that setting the DatabaseGenerated option tells Entity Framework that it doesn't need to send that parameter to your procs because the database will generate the value for that column/property. You DO have to remember that the stored proc MUST return that column in its resultset -- but it can just use the default value for that column, that was generated in the proc.
If you do this, the override for SaveChanges to set the default value isn't even needed, the value will be set for you after the SaveChanges().
FYI: Entity Framework 6.x here.

Related

EF Core Updating Entity Property with JSON Type

I have a 1 to many relationship of Parent and Child to store some data. I want to store this data in PostgreSQL using Npgsql.EntityFrameworkCore.PostgreSQL package. Parent maps to a table, but Child is stored as a json column of Parent table in the database.
public class Parent
{
public int Id { get; set; }
[Column(TypeName = "json")]
public ICollection<Child> Children { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
}
When I try to add a Child entity to an existing Parent instance with the code below, SaveChangesAsync doesn't produce an update command on the database.
var child = new Child(){Id = 0, Name = "Name"};
var parent = await DataContext.Parent.SingleOrDefaultAsync(f => f.Id == 1);
parent.Children.Add(child);
await DataContext.SaveChangesAsync();
In order to trigger an update command, I have to set parent entity's State to Modified before calling SaveChangesAsync.
var entry = DataContext.Entry<Parent>(parent);
entry.State = EntityState.Modified;
Is this the expected behavior or am I missing something?
Update:
As #SvyatoslavDanyliv suggested, instead of using ICollection when I use a class EqualityCollection derived from List and override Object.Equals as follows :
public class EqualityCollection<T> : List<T>
{
public override bool Equals(object? obj)
{
if (obj != null && obj.GetType() == typeof(EqualityCollection<T>))
return this.Equals(obj as EqualityCollection<T>);
return false;
}
public bool Equals(EqualityCollection<T> obj)
{
return this.SequenceEqual(obj ?? throw new InvalidOperationException());
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
Change detection doesn't detect the change in the property and update command is not triggered.
You have to define ValueComparer when defining conversion via HasConversion as described in documentation: Value Comparers

How to insert an entity and keep the same Object Guid with EF

I need keep the same original Id (GUID) after save data because is a replication job. (SQL -> SQL remote). Then, the model can not be changed. After SaveChanges() EF insert a new random Guid as Id, then this changes my original object, and do not want that. A compact sample:
class EFInsertTest
{
public void InsertTest()
{
var id = new Guid("D75C887D-BF25-E611-943B-080027BA87E8"); // dummy
var entity = new Something { Id = id, Name = "ELENOR" };
using (var db = new SomethingContext())
{
db.Things.Add(entity);
db.SaveChanges();
// TEST
if (db.Things.Find(id) != null)
{
Console.WriteLine($"Great! Expected behavior");
}
else
{// run this:
Console.WriteLine($"Failed! Id has another value");
}
Console.ReadKey();
// SQL hard code (works fine)
db.Database.ExecuteSqlCommand($"INSERT INTO [Something] VALUES('{id}', '{entity.Name}')");
db.SaveChanges();
// TEST
if (db.Things.Find(id) != null)
{
Console.WriteLine($"Great! Expected behavior");
}
else
{
Console.WriteLine($"Failed! Id has another value");
}
Console.ReadKey();
}
}
}
public class SomethingContext : DbContext
{
public virtual DbSet<Something> Things { get; set; }
}
public class Something
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string Name { get; set; }
}

EF Context not keeping values after adding entity

Edit Is this post lacking sufficient information to get some guidance?
I have this method to insert an entity into the database:
public void Insert(T entity)
{
_context.Set<T>().Add(entity);
_context.SaveChanges();
}
When I inspect entity before adding it to the context, my CustomerRole field is there. Once the add has taken place, the context doesn't seem to have it. Because of this, I am receiving this error:
Entities in 'CcDataContext.Customers' participate in the
'Customer_CustomerRole' relationship. 0 related
'Customer_CustomerRole_Target' were found. 1
'Customer_CustomerRole_Target' is expected.
These images show what I mean:
Inspecting my entity
Inspecting the context
Can anyone explain this behaviour and what I can do about it?
This is the structure of my classes (cut down for brevity):
public class Customer : BaseEntity
{
public CustomerRole CustomerRole { get; set; }
}
class CustomerMap : EntityTypeConfiguration<Customer>
{
public CustomerMap()
{
HasRequired(t => t.CustomerRole)
.WithMany(t => t.Customers);
}
}
public class CustomerRole : BaseEntity
{
private ICollection<Customer> _customers;
public ICollection<Customer> Customers
{
get { return _customers ?? (new List<Customer>()); }
set { _customers = value; }
}
}
I can confirm that customer map is being added to the configuration and my database is built in line with them.
This is the call I am making which does the insert:
public Customer InsertGuestCustomer()
{
var customer = new Customer();
CustomerRole guestRole = GetCustomerRoleByName("Guest");
if (guestRole == null)
throw new Exception("Customer Role is not defined!");
customer.UserName = "";
customer.EmailAddress = "";
customer.Password = "";
customer.IsAdmin = false;
customer.CustomerRole = guestRole;
_customerRepository.Insert(customer);
return customer;
}
I have no other data in my database, this would be the first customer record and only one CustomerRole. My Customer table has a Foreign Key pointing to my CustomerRole.Id table / column.
Mark your navigation properties as virtual and initialize the collection property in the entity constructor rather than from the property getter.
public class Customer : BaseEntity
{
public virtual CustomerRole CustomerRole { get; set; }
}
...
public class CustomerRole : BaseEntity
{
public CustomerRole()
{
Customers = new List<Customer>();
}
public virtual ICollection<Customer> Customers { get; protected set; }
}
In your Customers property, you were returning a new List in the getter when the backing field was null, but you never assigned this to your backing field.

Entity Framework 6 Code first Default value

is there "elegant" way to give specific property a default value ?
Maybe by DataAnnotations, something like :
[DefaultValue("true")]
public bool Active { get; set; }
Thank you.
You can do it by manually edit code first migration:
public override void Up()
{
AddColumn("dbo.Events", "Active", c => c.Boolean(nullable: false, defaultValue: true));
}
It's been a while, but leaving a note for others.
I achieved what is needed with an attribute and I decorated my model class fields with that attribute as I want.
[SqlDefaultValue(DefaultValue = "getutcdate()")]
public DateTime CreatedDateUtc { get; set; }
Got the help of these 2 articles:
EF on CodePlex
Andy Mehalick blog
What I did:
Define Attribute
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class SqlDefaultValueAttribute : Attribute
{
public string DefaultValue { get; set; }
}
In the "OnModelCreating" of the context
modelBuilder.Conventions.Add( new AttributeToColumnAnnotationConvention<SqlDefaultValueAttribute, string>("SqlDefaultValue", (p, attributes) => attributes.Single().DefaultValue));
In the custom SqlGenerator
private void SetAnnotatedColumn(ColumnModel col)
{
AnnotationValues values;
if (col.Annotations.TryGetValue("SqlDefaultValue", out values))
{
col.DefaultValueSql = (string)values.NewValue;
}
}
Then in the Migration Configuration constructor, register the custom SQL generator.
SetSqlGenerator("System.Data.SqlClient", new CustomMigrationSqlGenerator());
The above answers really helped, but only delivered part of the solution.
The major issue is that as soon as you remove the Default value attribute, the constraint on the column in database won't be removed. So previous default value will still stay in the database.
Here is a full solution to the problem, including removal of SQL constraints on attribute removal.
I am also re-using .NET Framework's native DefaultValue attribute.
Usage
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
[DefaultValue("getutcdate()")]
public DateTime CreatedOn { get; set; }
For this to work you need to update IdentityModels.cs and Configuration.cs files
IdentityModels.cs file
Add/update this method in your ApplicationDbContext class
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
var convention = new AttributeToColumnAnnotationConvention<DefaultValueAttribute, string>("SqlDefaultValue", (p, attributes) => attributes.SingleOrDefault().Value.ToString());
modelBuilder.Conventions.Add(convention);
}
Configuration.cs file
Update your Configuration class constructor by registering custom Sql generator, like this:
internal sealed class Configuration : DbMigrationsConfiguration<ApplicationDbContext>
{
public Configuration()
{
// DefaultValue Sql Generator
SetSqlGenerator("System.Data.SqlClient", new DefaultValueSqlServerMigrationSqlGenerator());
}
}
Next, add custom Sql generator class (you can add it to the Configuration.cs file or a separate file)
internal class DefaultValueSqlServerMigrationSqlGenerator : SqlServerMigrationSqlGenerator
{
private int dropConstraintCount;
protected override void Generate(AddColumnOperation addColumnOperation)
{
SetAnnotatedColumn(addColumnOperation.Column, addColumnOperation.Table);
base.Generate(addColumnOperation);
}
protected override void Generate(AlterColumnOperation alterColumnOperation)
{
SetAnnotatedColumn(alterColumnOperation.Column, alterColumnOperation.Table);
base.Generate(alterColumnOperation);
}
protected override void Generate(CreateTableOperation createTableOperation)
{
SetAnnotatedColumns(createTableOperation.Columns, createTableOperation.Name);
base.Generate(createTableOperation);
}
protected override void Generate(AlterTableOperation alterTableOperation)
{
SetAnnotatedColumns(alterTableOperation.Columns, alterTableOperation.Name);
base.Generate(alterTableOperation);
}
private void SetAnnotatedColumn(ColumnModel column, string tableName)
{
if (column.Annotations.TryGetValue("SqlDefaultValue", out var values))
{
if (values.NewValue == null)
{
column.DefaultValueSql = null;
using var writer = Writer();
// Drop Constraint
writer.WriteLine(GetSqlDropConstraintQuery(tableName, column.Name));
Statement(writer);
}
else
{
column.DefaultValueSql = (string)values.NewValue;
}
}
}
private void SetAnnotatedColumns(IEnumerable<ColumnModel> columns, string tableName)
{
foreach (var column in columns)
{
SetAnnotatedColumn(column, tableName);
}
}
private string GetSqlDropConstraintQuery(string tableName, string columnName)
{
var tableNameSplitByDot = tableName.Split('.');
var tableSchema = tableNameSplitByDot[0];
var tablePureName = tableNameSplitByDot[1];
var str = $#"DECLARE #var{dropConstraintCount} nvarchar(128)
SELECT #var{dropConstraintCount} = name
FROM sys.default_constraints
WHERE parent_object_id = object_id(N'{tableSchema}.[{tablePureName}]')
AND col_name(parent_object_id, parent_column_id) = '{columnName}';
IF #var{dropConstraintCount} IS NOT NULL
EXECUTE('ALTER TABLE {tableSchema}.[{tablePureName}] DROP CONSTRAINT [' + #var{dropConstraintCount} + ']')";
dropConstraintCount++;
return str;
}
}
Your model properties don't have to be 'auto properties' Even though that is easier. And the DefaultValue attribute is really only informative metadata
The answer accepted here is one alternative to the constructor approach.
public class Track
{
private const int DEFAULT_LENGTH = 400;
private int _length = DEFAULT_LENGTH;
[DefaultValue(DEFAULT_LENGTH)]
public int LengthInMeters {
get { return _length; }
set { _length = value; }
}
}
vs.
public class Track
{
public Track()
{
LengthInMeters = 400;
}
public int LengthInMeters { get; set; }
}
This will only work for applications creating and consuming data using this specific class. Usually this isn't a problem if data access code is centralized. To update the value across all applications you need to configure the datasource to set a default value. Devi's answer shows how it can be done using migrations, sql, or whatever language your data source speaks.
What I did, I initialized values in the constructor of the entity
Note: DefaultValue attributes won't set the values of your properties automatically, you have to do it yourself
I admit that my approach escapes the whole "Code First" concept. But if you have the ability to just change the default value in the table itself... it's much simpler than the lengths that you have to go through above... I'm just too lazy to do all that work!
It almost seems as if the posters original idea would work:
[DefaultValue(true)]
public bool IsAdmin { get; set; }
I thought they just made the mistake of adding quotes... but alas no such intuitiveness. The other suggestions were just too much for me (granted I have the privileges needed to go into the table and make the changes... where not every developer will in every situation). In the end I just did it the old fashioned way. I set the default value in the SQL Server table... I mean really, enough already! NOTE: I further tested doing an add-migration and update-database and the changes stuck.
After #SedatKapanoglu comment, I am adding all my approach that works, because he was right, just using the fluent API does not work.
1- Create custom code generator and override Generate for a ColumnModel.
public class ExtendedMigrationCodeGenerator : CSharpMigrationCodeGenerator
{
protected override void Generate(ColumnModel column, IndentedTextWriter writer, bool emitName = false)
{
if (column.Annotations.Keys.Contains("Default"))
{
var value = Convert.ChangeType(column.Annotations["Default"].NewValue, column.ClrDefaultValue.GetType());
column.DefaultValue = value;
}
base.Generate(column, writer, emitName);
}
}
2- Assign the new code generator:
public sealed class Configuration : DbMigrationsConfiguration<Data.Context.EfSqlDbContext>
{
public Configuration()
{
CodeGenerator = new ExtendedMigrationCodeGenerator();
AutomaticMigrationsEnabled = false;
}
}
3- Use fluent api to created the Annotation:
public static void Configure(DbModelBuilder builder){
builder.Entity<Company>().Property(c => c.Status).HasColumnAnnotation("Default", 0);
}
It's simple! Just annotate with required.
[Required]
public bool MyField { get; set; }
the resultant migration will be:
migrationBuilder.AddColumn<bool>(
name: "MyField",
table: "MyTable",
nullable: false,
defaultValue: false);
If you want true, change the defaultValue to true in the migration before updating the database
In .NET Core 3.1 you can do the following in the model class:
public bool? Active { get; set; }
In the DbContext OnModelCreating you add the default value.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Foundation>()
.Property(b => b.Active)
.HasDefaultValueSql("1");
base.OnModelCreating(modelBuilder);
}
Resulting in the following in the database
Note:
If you don't have nullable (bool?) for you property you will get the following warning
The 'bool' property 'Active' on entity type 'Foundation' is configured with a database-generated default. This default will always be used for inserts when the property has the value 'false', since this is the CLR default for the 'bool' type. Consider using the nullable 'bool?' type instead so that the default will only be used for inserts when the property value is 'null'.
I found that just using Auto-Property Initializer on entity property is enough to get the job done.
For example:
public class Thing {
public bool IsBigThing{ get; set; } = false;
}
using System.ComponentModel;
[DefaultValue(true)]
public bool Active { get; set; }
In EF core released 27th June 2016 you can use fluent API for setting default value. Go to ApplicationDbContext class, find/create the method name OnModelCreating and add the following fluent API.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<YourTableName>()
.Property(b => b.Active)
.HasDefaultValue(true);
}
Just Overload the default constructor of Model class and pass any relevant parameter which you may or may not use. By this you can easily supply default values for attributes. Below is an example.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Aim.Data.Domain
{
[MetadataType(typeof(LoginModel))]
public partial class Login
{
public Login(bool status)
{
this.CreatedDate = DateTime.Now;
this.ModifiedDate = DateTime.Now;
this.Culture = "EN-US";
this.IsDefaultPassword = status;
this.IsActive = status;
this.LoginLogs = new HashSet<LoginLog>();
this.LoginLogHistories = new HashSet<LoginLogHistory>();
}
}
public class LoginModel
{
[Key]
[ScaffoldColumn(false)]
public int Id { get; set; }
[Required]
public string LoginCode { get; set; }
[Required]
public string Password { get; set; }
public string LastPassword { get; set; }
public int UserGroupId { get; set; }
public int FalseAttempt { get; set; }
public bool IsLocked { get; set; }
public int CreatedBy { get; set; }
public System.DateTime CreatedDate { get; set; }
public Nullable<int> ModifiedBy { get; set; }
public Nullable<System.DateTime> ModifiedDate { get; set; }
public string Culture { get; set; }
public virtual ICollection<LoginLog> LoginLogs { get; set; }
public virtual ICollection<LoginLogHistory> LoginLogHistories { get; set; }
}
}
Even from .NET Core 1.0, It is possible to set default values when you are using the code first approach. See the following code snippet.
using System.ComponentModel;
[DefaultValue(true)]
public bool Active { get; set; }
Read for more: Microsoft official docs
Lets consider you have a class name named Products and you have a IsActive field. just you need a create constructor :
Public class Products
{
public Products()
{
IsActive = true;
}
public string Field1 { get; set; }
public string Field2 { get; set; }
public bool IsActive { get; set; }
}
Then your IsActive default value is True!
Edite :
if you want to do this with SQL use this command :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.IsActive)
.HasDefaultValueSql("true");
}
The Entity Framework Core Fluent API HasDefaultValue method is used to specify the default value for a database column mapped to a property. The value must be a constant.
public class Contact
{
public int ContactId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public bool IsActive { get; set; }
public DateTime DateCreated { get; set; }
}
public clas SampleContext : DbContext
{
public DbSet<Contact> Contacts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Context>()
.Propery(p => p.IsActive)
.HasDefaultValue(true);
}
}
Or
like it!
You can also specify a SQL fragment that is used to calculate the default value:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Created)
.HasDefaultValueSql("getdate()");
}
Hmm... I do DB first, and in that case, this is actually a lot easier. EF6 right? Just open your model, right click on the column you want to set a default for, choose properties, and you will see a "DefaultValue" field. Just fill that out and save. It will set up the code for you.
Your mileage may vary on code first though, I haven't worked with that.
The problem with a lot of other solutions, is that while they may work initially, as soon as you rebuild the model, it will throw out any custom code you inserted into the machine-generated file.
This method works by adding an extra property to the edmx file:
<EntityType Name="Thingy">
<Property Name="Iteration" Type="Int32" Nullable="false" **DefaultValue="1"** />
And by adding the necessary code to the constructor:
public Thingy()
{
this.Iteration = 1;
Set the default value for the column in table in MSSQL Server, and in class code add attribute, like this:
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
for the same property.

ef4 record stamping, inserted_at, inserted_by

is there any way of going through all the new/modified entities and setting their, inserted_at, updated_at fields?
With ObjectStateManager I can get a list of those entities but could not find a way of setting the entity property values.
foreach (var item in db.ObjectStateManager.GetObjectStateEntries(EntityState.Added))
{
System.Data.Objects.DataClasses.EntityObject entity = (System.Data.Objects.DataClasses.EntityObject)(item.Entity);
// now how can I set its .inserted_at to DateTime.Now
}
here is my current solution
public interface IUpdateTrack
{
DateTime? updated_at { get; set; }
Guid? updated_by { get; set; }
}
public interface IInsertTrack
{
DateTime? inserted_at { get; set; }
Guid? inserted_by { get; set; }
}
implement the interface in the partial class
public partial class crm_customer : BaseDB.IInsertTrack, BaseDB.IUpdateTrack
in the repository class
public void Save()
{
foreach (var item in db.ObjectStateManager.GetObjectStateEntries(EntityState.Added))
{
System.Data.Objects.DataClasses.EntityObject entity = (System.Data.Objects.DataClasses.EntityObject)(item.Entity);
if (item.Entity is BaseDB.IInsertTrack)
{
IInsertTrack insert_track = (IInsertTrack)(item.Entity);
insert_track.inserted_at = DateTime.Now;
insert_track.inserted_by = BaseDB.SessionContext.Current.ActiveUser.UserUid;
}
}
foreach (var item in db.ObjectStateManager.GetObjectStateEntries(EntityState.Modified))
{
if (item.Entity is BaseDB.IUpdateTrack)
{
IUpdateTrack update_track = (IUpdateTrack)(item.Entity);
update_track.updated_at = DateTime.Now;
update_track.updated_by = BaseDB.SessionContext.Current.ActiveUser.UserUid;
}
}
I would like a solution that does not require implementing the interface for each class in the model, its error prone, you might forget to implement this interfaces for some classes.
I am using EF4 using database-first approach.
Yes, there is a perfect way to accomplish this in Entity Framework 4.0, Thanks to Julia Lerman for pointing out this nice trick.
using System.Data.Common;
using System.Data.Metadata.Edm;
...
var entries = from e in db.ObjectStateManager.GetObjectStateEntries(
EntityState.Added | EntityState.Modified)
where e.Entity != null
select e;
foreach (var entry in entries) {
var fieldMetaData = entry.CurrentValues.DataRecordInfo.FieldMetadata;
FieldMetadata updatedAtField = fieldMetaData
.Where(f => f.FieldType.Name == "updated_at").FirstOrDefault();
if (updatedAtField.FieldType != null) {
string fieldTypeName = updatedAtField.FieldType.TypeUsage.EdmType.Name;
if (fieldTypeName == PrimitiveTypeKind.DateTime.ToString()) {
entry.CurrentValues.SetDateTime(updatedAtField.Ordinal,
DateTime.Now);
}
}
}
You can then call this code from within the SavingChanges event to be sure that any
updated_at field is automatically updated.
By the way, the System.Data.Metadata.Edm namespace gives you access to
the PrimitiveTypeKind class.