Entity object property with a timestamp/rowversion field? - c#

I am using entity framework 6 and i have a base entity called EntityBase, very simple as so
public abstract class EntityBase : IEntityBase
{
public virtual long Id { get; set; }
}
Each of my entities inherit from this. Now some entities need audit information, so i have first created an interface called IAudit
public interface IAudit
{
Audit Audit { get; set; }
}
And the Audit object like so
public class Audit
{
public bool IsDeleted { get; set; }
public DateTime? DeletedDate { get; set; }
public long? DeletedByUserId { get; set; }
public DateTime CreatedDate { get; set; }
public long CreatedByUserId { get; set; }
public DateTime UpdatedDate { get; set; }
public long UpdatedByUserId { get; set; }
public byte[] RowVersion { get; set; }
}
And if an entity needs audit information, i apply this interface. Here is an example
public class Attachment : EntityBase, IAudit
{
#region IAudit
public Audit Audit { get; set; }
#endregion
public string Name { get; set; }
}
I am using code first, so now in my DbContext, i have this in my OnModelCreating method
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Configurations.Add(new AttachmentConfig());
}
Here is my AttachmentConfig file, which inherits from EntityBaseConfig, both included below
public abstract class EntityBaseConfig<TEntity> : EntityTypeConfiguration<TEntity>
where TEntity : EntityBase
{
public EntityBaseConfig()
{
this.HasKey(e => e.Id);
}
}
class AttachmentConfig : EntityBaseConfig<Attachment>
{
public AttachmentConfig()
: base()
{
this.Property(e => e.Name)
.HasMaxLength(255)
.IsRequired();
}
}
Now when my table is created, the Attachment table has the columns i would expect, but Audit_RowVersion is varbinary(max) instead of timestamp.
I have tried to put this line in each config file but get this error.
this.Property(e => e.Audit.RowVersion).IsRowVersion();
Schema specified is not valid. Errors: (36,14) : error 2039: The
conceptual side property 'RowVersion' has already been mapped to a
storage property with type 'rowversion'. If the conceptual side
property is mapped to multiple properties in the storage model, make
sure that all the properties in the storage model have the same type.
But i do not want to write this line in each file anyway. How can i get the Audit.RowVersion column to generate as a timestamp, and ideally write it once, so that all objects that implement IAudit also get the configured fields?
EDIT:
I have now added a config file for the Audit object, which looks like this
public class AuditConfig : EntityTypeConfiguration<Audit>
{
public AuditConfig() : base()
{
this.Property(e => e.RowVersion)
.IsRowVersion();
}
}
And i call this in the OnModelCreating method, like so, before the other Configuration calls
modelBuilder.Configurations.Add(new AuditConfig());
Now when i run my project, i get the following error
A table can only have one timestamp column. Because table 'Attachment'
already has one, the column 'Audit_RowVersion' cannot be added.
If i look at the database and Attachment table created so far, it has the fields in the Audit object, but they do not have the Audit_ prefix? Maybe this is a clue to someone?

You can use DataAnnotations.
In your model class
public class Audit
{
public bool IsDeleted { get; set; }
public DateTime? DeletedDate { get; set; }
public long? DeletedByUserId { get; set; }
public DateTime CreatedDate { get; set; }
public long CreatedByUserId { get; set; }
public DateTime UpdatedDate { get; set; }
public long UpdatedByUserId { get; set; }
[TimeStamp]
public byte[] RowVersion { get; set; }
}
You can add [TimeStamp] Attribute for your variable.

Related

How to have multiple tables with the same schema in Entity Framework?

I'm trying to have multiple tables with the same schema, within the same database, using Entity Framework.
For example, if I have the classes below, and I login to the SQL Server database, I can only see a table that is named something like dbo.Schema.
Is there a way to have multiple tables with the same schema?
class Context1 : DbContext
{
public DbSet<Schema> table1 { get; set; }
}
class Context2 : DbContext
{
public DbSet<Schema> table2 { get; set; }
}
class Schema
{
[Key]
public int EntryId { get; set; }
}
Is there a way to have multiple tables with the same schema?
You can either use Data Annotations or Fluent API to configure the table and schema name.
Suppose you have the following model:
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
}
Using Data Annotations, you could name it blogging.blogs:
[Table("blogs", Schema = "blogging")]
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
}
Using Fluent API, you can override OnModelCreating method to name it blogging.blogs:
class MyContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.ToTable("blogs", schema: "blogging");
}
}
You can simple do like this with multiple tables.
public partial class AdventureWorksEntities : DbContext
{
public AdventureWorksEntities()
: base("name=AdventureWorksEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Address> Addresses { get; set; }
public virtual DbSet<AddressType> AddressTypes { get; set; }
public virtual DbSet<Contact> Contacts { get; set; }
public virtual DbSet<ContactType> ContactTypes { get; set; }
public virtual DbSet<CountryRegion> CountryRegions { get; set; }
public virtual DbSet<StateProvince> StateProvinces { get; set; }
}
in this code we can add more table from same database. There is no need to create another class and inherit DbContext.
or you can do Add Item into project-> New Item->Data->ADO.NET Entity Data Model.
This will generate same code with your selected tables.
Thanks

Table per Concrete Type with tables as context properties

I am trying to create a base class for all of my tables in order to make sure I have some data like CreateDate/Person, etc on all of my data.
So basically, I want my tables to inherit from a base class, but I want to have that data on each table separably (table per concrete type).
I've found some tutorials like these, but the problem with them is that I won't have any strongly named property for my tables.
weblogs.asp.net
As you see, in the link he created a dbset of the base class and there is no relation to the concrete class.
public class InheritanceMappingContext : DbContext
{
public DbSet<BillingDetail> BillingDetails { get; set; }
}
Here is my code:
public abstract class TrackableBase
{
public int Id { get; set; }
public virtual DateTime CreateDate { get; protected set; }
public TrackableBase()
{
CreateDate = DateTime.Now;
}
}
public class User : TrackableBase
{
public string Name { get; set; }
public string Email { get; set; }
}
You should add a property of type DbSet for each class and add the OnModelCreating for the mapping
public class InheritanceMappingContext : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Users");
});
}
}

Entity framework creating additional ID field

I am developing an application where I am using Entity framework. I have an abstract class called BaseEntity which is inherited by all other classes; it contains properties which are common to all other classes - Id(PK), CreatedDate, ModifiedDate, CreatedBy and ModifiedBy.
public abstract class BaseEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public DateTime CreatedDate
{
get ; set ;
}
public DateTime? ModifiedDate
{
get; set;
}
public string CreatedBy { get; set; }
public string ModifiedBy { get; set; }
[Timestamp]
public byte[] Version { get; set; }
}
I have a class called City which inherits the above class
public class City : BaseEntity
{
public string Name { get; set; }
}
The problem I am having now is Entity framework is rightfully inserting the PK value in the column Id but its creating an extra column City_Id which is null. Since I am using the Id column from the base class as the PK I don't need the City_Id column. How can I get rid of it? I know it's the convention of Entity Framework to use ID or Type_Id as the PK.
Fluent API configuration
public class CityConfig : EntityTypeConfiguration<City>
{
public CityConfig()
{
Property(x => x.Name).IsRequired().HasMaxLength(50);
Property(x => x.CreatedBy).IsRequired().HasMaxLength(50);
Property(x => x.ModifiedBy).IsRequired().HasMaxLength(50);
}
}
And my context is as shown below
public virtual DbSet<City> Cities { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//Other lines removed for brevity
modelBuilder.Configurations.Add(new CityConfig());
}

ef code first mapping one table into some entities

I try to solve following:
In the entities HRCard and BPCard I have properties
public int DefaultAddressId { get; set; }
[ForeignKey("DefaultAddressId")]
public AddressDetail Address { get; set; } // table AddressDetail
so far no problem, now my Problem:
In BPCard i have additionally a property:
public virtual ICollection<AddressDetail> Addresses { get; set; } //table AddressDetail
following the complete Code:
public abstract class EntityBase : IEntityModel {
[Key]
public int EntityId { get; set; }
[Required]
[StringLength(50)]
public string EntityKey { get; set; }
//...
}
// table HRCards
public class HRCard : EntityBase {
//Id from base class
// working fine
//...
public int DefaultAddressId { get; set; }
[ForeignKey("DefaultAddressId")]
public AddressDetail Address { get; set; } // table AddressDetail
}
// table BPCards
public class BPCard : EntityBase {
//Id from base class
// working fine
//...
public int DefaultAddressId { get; set; }
public int DefaultContactId { get; set; }
//working fine
[ForeignKey("DefaultAddressId")]
public AddressDetail DefaultAddress { get; set; } //table AddressDetail
//how can i solve this??
// table AddressDetail
public virtual ICollection<AddressDetail> Addresses { get; set; }
}
public class AddressDetail : EntityBase {
//Id from base class
// working fine
//...
public int ParentId { get; set; }
}
I have long time searched, but no result solve my problem really. My 1st solution was split the table into HRAddress and BPAddress this is working fine.
Edit:
If I start enable migrations I get an error message:
"The property 'ParentId' cannot be configured as a navigation property. The property must be a valid entity type and the property should have a non-abstract getter and setter. For collection properties the type must implement ICollection where T is a valid entity type."
many thanks
PS:
can I change the tags later for better mapping?
It depends on the expected relationship you want with the Address POCO
You can solve the relationship using the annotation with something like
public class AddressDetail : EntityBase {
//Id from base class
public virtual ICollection<BPCard> Addresses { get; set; }
//public virtual BPCard Addresses { get; set; }
public virtual ICollection<HRCard> Addresses { get; set; }
//public virtual BPCard Addresses { get; set; }
}
or directly on your model with something like
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//one-to-many
modelBuilder.Entity<HRCard>()
.HasMany<AddressDetails>(s => s.Id)
.WithRequired(s => s.HRCard)
.HasForeignKey(s => s.AddressId);
}

Entity Framework Code First Common Database Audit Fields

I'm new to Entity Framework, in the past I've used Enterprise Library or ADO.NET directly to map models to database tables. One pattern that I've used is to put my common audit fields that appear in every table in a Base Class and then inherit that Base Class for every object.
I take two steps to protect two of the fields (Created, CreatedBy):
Have a parameterless constructor private on the Base Enitity and create a second one that requires Created and CreatedBy are passed on creation.
Make the setters Private so that the values cannot be changed after the object is created.
Base Class:
using System;
namespace App.Model
{
[Serializable()]
public abstract class BaseEntity
{
public bool IsActive { get; private set; }
public DateTimeOffset Created { get; private set; }
public string CreatedBy { get; private set; }
public DateTimeOffset LastUpdated { get; protected set; }
public string LastUpdatedBy { get; protected set; }
private BaseEntity() { }
protected BaseEntity(DateTimeOffset created, string createdBy)
{
IsActive = true;
Created = created;
CreatedBy = createdBy;
LastUpdated = created;
LastUpdatedBy = createdBy;
}
}
}
Inherited Class:
using System;
namespace App.Model
{
[Serializable()]
public class Person : BaseEntity
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public Person(DateTimeOffset created, string createdBy) :
base(created, createdBy) { }
}
}
I've run into issues with both. EF requires a parameterless constructor to create objects. EF will not create the database columns that have a private setter.
My question is if there is a better approach to accomplish my goals with EF:
Require that the values for Created and CreatedBy are populated at instantiation.
The values of Created and CreatedBy cannot be changed.
You could instantiate a context with a constructor that accepts a string createdBy. Then in an override of SaveChanges():
public override int SaveChanges()
{
foreach( var entity in ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added)
.Select (e => e.Entity)
.OfType<BaseEntity>())
{
entity.SetAuditValues(DateTimeOffset.Now, this.CreatedBy);
}
return base.SaveChanges();
}
With SetAuditValues() as
internal void SetAuditValues(DateTimeOffset created, string createdBy)
{
if (this.Created == DateTimeOffset.MinValue) this.Created = created;
if (string.IsNullOrEmpty(this.CreatedBy)) this.CreatedBy = createdBy;
}
After the entities have been materialized from the database the values won't be overwritten when someone calls SetAuditValues.
You shouldn't be trying to control access rights directly on your entities/data layer instead your should do this in your application layer. This way you have a finer level of control over what users can do what.
Also rather than have the audit fields repeated on every table you might want to store your Audit records in another table. This is easy to do with code first:
public class AuitRecord
{
public bool IsActive { get; set; }
public DateTimeOffset Created { get; set; }
public string CreatedBy { get; set; }
public DateTimeOffset LastUpdated { get; set; }
public string LastUpdatedBy { get; set; }
}
You would then link the base class with the audit record to it:
public abstract class BaseEntity
{
public AuditRecord Audit { get; set; }
}
And finally your actually entities
public class Person : BaseEntity
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
You can no access the audit data by going:
Person.Audit.IsActive

Categories

Resources