Logging every data change with Entity Framework - entity-framework

There is a need from a customer to log every data change to a logging table with the actual user who made the modification. The application is using one SQL user to access the database, but we need to log the "real" user id.
We can do this in t-sql by writing triggers for every table insert and update, and using context_info to store the user id. We passed the user id to a stored procedure, stored the user id in the contextinfo, and the trigger could use this info to write log rows to the log table.
I can not find the place or way where or how can I do something similar using EF. So the main goal is: if I make a change in the data via EF, I would like to log the exact data change to a table in a semi-automatic way (so I don't want to check for every field for change before saving the object). We are using EntitySQL.
Unfortunately we have to stick on SQL 2000 so the data change capture introduced in SQL2008 is not an option (but maybe that's also not the right way for us).
Any ideas, links or starting points?
[Edit]
Some notes: by using ObjectContext.SavingChanges eventhandler, I can get the point where I can inject the SQL statement to initialize the contextinfo. However I cannot mix the EF and the standard SQL. So I can get the EntityConnection but I cannot execute a T-SQL statement using it. Or I can get the connection string of the EntityConnection and create an SqlConnection based on it, but it will be a different connection, so the contextinfo will not affect the save made by the EF.
I tried the following in the SavingChanges handler:
testEntities te = (testEntities)sender;
DbConnection dc = te.Connection;
DbCommand dcc = dc.CreateCommand();
dcc.CommandType = CommandType.StoredProcedure;
DbParameter dp = new EntityParameter();
dp.ParameterName = "userid";
dp.Value = textBox1.Text;
dcc.CommandText = "userinit";
dcc.Parameters.Add(dp);
dcc.ExecuteNonQuery();
Error: The value of EntityCommand.CommandText is not valid for a StoredProcedure command.
The same with SqlParameter instead of EntityParameter: SqlParameter cannot be used.
StringBuilder cStr = new StringBuilder("declare #tx char(50); set #tx='");
cStr.Append(textBox1.Text);
cStr.Append("'; declare #m binary(128); set #m = cast(#tx as binary(128)); set context_info #m;");
testEntities te = (testEntities)sender;
DbConnection dc = te.Connection;
DbCommand dcc = dc.CreateCommand();
dcc.CommandType = CommandType.Text;
dcc.CommandText = cStr.ToString();
dcc.ExecuteNonQuery();
Error: The query syntax is not valid.
So here I am, stuck to create a bridge between Entity Framework and ADO.NET.
If I can get it working, I will post a proof of concept.

How about handling Context.SavingChanges?

Thanks for pointing me in the right direction. However, in my case, I also need to set the context info when doing select statements, because I am querying views that use the context info to control row-level security by user.
I found it easiest to attach to the StateChanged event of the connection and just watch for the change from not-open to open. Then I call the proc that sets context and it works every time, even if EF decides to reset the connection.
private int _contextUserId;
public void SomeMethod()
{
var db = new MyEntities();
db.Connection.StateChange += this.Connection_StateChange;
this._contextUserId = theCurrentUserId;
// whatever else you want to do
}
private void Connection_StateChange(object sender, StateChangeEventArgs e)
{
// only do this when we first open the connection
if (e.OriginalState == ConnectionState.Open ||
e.CurrentState != ConnectionState.Open)
return;
// use the existing open connection to set the context info
var connection = ((EntityConnection) sender).StoreConnection;
var command = connection.CreateCommand();
command.CommandText = "proc_ContextInfoSet";
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add(new SqlParameter("ContextUserID", this._contextUserId));
command.ExecuteNonQuery();
}

Finally with Craig's help, here is a proof of concept. It needs more testing, but for first look it is working.
First: I created two tables, one for data one for logging.
-- This is for the data
create table datastuff (
id int not null identity(1, 1),
userid nvarchar(64) not null default(''),
primary key(id)
)
go
-- This is for the log
create table naplo (
id int not null identity(1, 1),
userid nvarchar(64) not null default(''),
datum datetime not null default('2099-12-31'),
primary key(id)
)
go
Second: create a trigger for insert.
create trigger myTrigger on datastuff for insert as
declare #User_id int,
#User_context varbinary(128),
#User_id_temp varchar(64)
select #User_context = context_info
from master.dbo.sysprocesses
where spid=##spid
set #User_id_temp = cast(#User_context as varchar(64))
declare #insuserid nvarchar(64)
select #insuserid=userid from inserted
insert into naplo(userid, datum)
values(#User_id_temp, getdate())
go
You should also create a trigger for update, which will be a little bit more sophisticated, because it needs to check every field for changed content.
The log table and the trigger should be extended to store the table and field which is created/changed, but I hope you got the idea.
Third: create a stored procedure which fills in the user id to the SQL context info.
create procedure userinit(#userid varchar(64))
as
begin
declare #m binary(128)
set #m = cast(#userid as binary(128))
set context_info #m
end
go
We are ready with the SQL side. Here comes the C# part.
Create a project and add an EDM to the project. The EDM should contain the datastuff table (or the tables you need to watch for changes) and the SP.
Now do something with the entity object (for example add a new datastuff object) and hook to the SavingChanges event.
using (testEntities te = new testEntities())
{
// Hook to the event
te.SavingChanges += new EventHandler(te_SavingChanges);
// This is important, because the context info is set inside a connection
te.Connection.Open();
// Add a new datastuff
datastuff ds = new datastuff();
// This is coming from a text box of my test form
ds.userid = textBox1.Text;
te.AddTodatastuff(ds);
// Save the changes
te.SaveChanges(true);
// This is not needed, only to make sure
te.Connection.Close();
}
Inside the SavingChanges we inject our code to set the context info of the connection.
// Take my entity
testEntities te = (testEntities)sender;
// Get it's connection
EntityConnection dc = (EntityConnection )te.Connection;
// This is important!
DbConnection storeConnection = dc.StoreConnection;
// Create our command, which will call the userinit SP
DbCommand command = storeConnection.CreateCommand();
command.CommandText = "userinit";
command.CommandType = CommandType.StoredProcedure;
// Put the user id as the parameter
command.Parameters.Add(new SqlParameter("userid", textBox1.Text));
// Execute the command
command.ExecuteNonQuery();
So before saving the changes, we open the object's connection, inject our code (don't close the connection in this part!) and save our changes.
And don't forget! This needs to be extended for your logging needs, and needs to be well tested, because this show only the possibility!

Have you tried adding the stored procedure to your entity model?

Simply force an execution of the SET CONTEXT_INFO by using your DbContext or ObjectContext:
...
FileMoverContext context = new FileMoverContext();
context.SetSessionContextInfo(Environment.UserName);
...
context.SaveChanges();
FileMoverContext inherits from DbContext and has a SetSessionContextInfo method.
Here is what my SetSessionContextInfo(...) looks like:
public bool SetSessionContextInfo(string infoValue)
{
try
{
if (infoValue == null)
throw new ArgumentNullException("infoValue");
string rawQuery =
#"DECLARE #temp varbinary(128)
SET #temp = CONVERT(varbinary(128), '";
rawQuery = rawQuery + infoValue + #"');
SET CONTEXT_INFO #temp";
this.Database.ExecuteSqlCommand(rawQuery);
return true;
}
catch (Exception e)
{
return false;
}
}
Now you just set up a database trigger which can access the CONTEXT_INFO() and set a database field using it.

We had solve this problem in a different way.
Inherit a class from your generated entity container class
Make the base entity class abstract. You can do it by a partial class definition in a separate file
In the inherited class hide the SavingChanges method with your own, using the new keyword in the method definition
In your SavingChanges method:
a, open an entity connection
execute the user context stored procedure with ebtityclient
call base.SaveChanges()
close the entityconnection
In your code you have to use the inherited class then.

I had somewhat similar scenario, which I resolved through following steps:
First create a generic repository for all CRUD operations like following, which is always a good approach.
public class GenericRepository : IGenericRepository where T : class
Now write your actions like "public virtual void Update(T entityToUpdate)".
Wherever you required logging / Auditing; just call a user defined function as follows "LogEntity(entityToUpdate, "U");".
Refer below pasted file/class to define "LogEntity" function. In this function, in case of update and delete we would get the old entity through primary key to insert in audit table. To identify primary key and get its value I used reflection.
Find reference of complete class below:
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
internal SampleDBContext Context;
internal DbSet<T> DbSet;
/// <summary>
/// Constructor to initialize type collection
/// </summary>
/// <param name="context"></param>
public GenericRepository(SampleDBContext context)
{
Context = context;
DbSet = context.Set<T>();
}
/// <summary>
/// Get query on current entity
/// </summary>
/// <returns></returns>
public virtual IQueryable<T> GetQuery()
{
return DbSet;
}
/// <summary>
/// Performs read operation on database using db entity
/// </summary>
/// <param name="filter"></param>
/// <param name="orderBy"></param>
/// <param name="includeProperties"></param>
/// <returns></returns>
public virtual IEnumerable<T> Get(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>,
IOrderedQueryable<T>> orderBy = null, string includeProperties = "")
{
IQueryable<T> query = DbSet;
if (filter != null)
{
query = query.Where(filter);
}
query = includeProperties.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Aggregate(query, (current, includeProperty) => current.Include(includeProperty));
if (orderBy == null)
return query.ToList();
else
return orderBy(query).ToList();
}
/// <summary>
/// Performs read by id operation on database using db entity
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public virtual T GetById(object id)
{
return DbSet.Find(id);
}
/// <summary>
/// Performs add operation on database using db entity
/// </summary>
/// <param name="entity"></param>
public virtual void Insert(T entity)
{
//if (!entity.GetType().Name.Contains("AuditLog"))
//{
// LogEntity(entity, "I");
//}
DbSet.Add(entity);
}
/// <summary>
/// Performs delete by id operation on database using db entity
/// </summary>
/// <param name="id"></param>
public virtual void Delete(object id)
{
T entityToDelete = DbSet.Find(id);
Delete(entityToDelete);
}
/// <summary>
/// Performs delete operation on database using db entity
/// </summary>
/// <param name="entityToDelete"></param>
public virtual void Delete(T entityToDelete)
{
if (!entityToDelete.GetType().Name.Contains("AuditLog"))
{
LogEntity(entityToDelete, "D");
}
if (Context.Entry(entityToDelete).State == EntityState.Detached)
{
DbSet.Attach(entityToDelete);
}
DbSet.Remove(entityToDelete);
}
/// <summary>
/// Performs update operation on database using db entity
/// </summary>
/// <param name="entityToUpdate"></param>
public virtual void Update(T entityToUpdate)
{
if (!entityToUpdate.GetType().Name.Contains("AuditLog"))
{
LogEntity(entityToUpdate, "U");
}
DbSet.Attach(entityToUpdate);
Context.Entry(entityToUpdate).State = EntityState.Modified;
}
public void LogEntity(T entity, string action = "")
{
try
{
//*********Populate the audit log entity.**********
var auditLog = new AuditLog();
auditLog.TableName = entity.GetType().Name;
auditLog.Actions = action;
auditLog.NewData = Newtonsoft.Json.JsonConvert.SerializeObject(entity);
auditLog.UpdateDate = DateTime.Now;
foreach (var property in entity.GetType().GetProperties())
{
foreach (var attribute in property.GetCustomAttributes(false))
{
if (attribute.GetType().Name == "KeyAttribute")
{
auditLog.TableIdValue = Convert.ToInt32(property.GetValue(entity));
var entityRepositry = new GenericRepository<T>(Context);
var tempOldData = entityRepositry.GetById(auditLog.TableIdValue);
auditLog.OldData = tempOldData != null ? Newtonsoft.Json.JsonConvert.SerializeObject(tempOldData) : null;
}
if (attribute.GetType().Name == "CustomTrackAttribute")
{
if (property.Name == "BaseLicensingUserId")
{
auditLog.UserId = ValueConversion.ConvertValue(property.GetValue(entity).ToString(), 0);
}
}
}
}
//********Save the log in db.*********
new UnitOfWork(Context, null, false).AuditLogRepository.Insert(auditLog);
}
catch (Exception ex)
{
Logger.LogError(string.Format("Error occured in [{0}] method of [{1}]", Logger.GetCurrentMethod(), this.GetType().Name), ex);
}
}
}
CREATE TABLE [dbo].[AuditLog](
[AuditId] [BIGINT] IDENTITY(1,1) NOT NULL,
[TableName] [nvarchar](250) NULL,
[UserId] [int] NULL,
[Actions] [nvarchar](1) NULL,
[OldData] [text] NULL,
[NewData] [text] NULL,
[TableIdValue] [BIGINT] NULL,
[UpdateDate] [datetime] NULL,
CONSTRAINT [PK_DBAudit] PRIMARY KEY CLUSTERED
(
[AuditId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY =
OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

This is what I used found here I modified it because it didn't work
private object GetPrimaryKeyValue(DbEntityEntry entry)
{
var objectStateEntry = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager.GetObjectStateEntry(entry.Entity);
object o = objectStateEntry.EntityKey.EntityKeyValues[0].Value;
return o;
}
private bool inExcludeList(string prop)
{
string[] excludeList = { "props", "to", "exclude" };
return excludeList.Any(s => s.Equals(prop));
}
public int SaveChanges(User user, string UserId)
{
var modifiedEntities = ChangeTracker.Entries()
.Where(p => p.State == EntityState.Modified).ToList();
var now = DateTime.Now;
foreach (var change in modifiedEntities)
{
var entityName = ObjectContext.GetObjectType(change.Entity.GetType()).Name;
var primaryKey = GetPrimaryKeyValue(change);
var DatabaseValues = change.GetDatabaseValues();
foreach (var prop in change.OriginalValues.PropertyNames)
{
if(inExcludeList(prop))
{
continue;
}
string originalValue = DatabaseValues.GetValue<object>(prop)?.ToString();
string currentValue = change.CurrentValues[prop]?.ToString();
if (originalValue != currentValue)
{
ChangeLog log = new ChangeLog()
{
EntityName = entityName,
PrimaryKeyValue = primaryKey.ToString(),
PropertyName = prop,
OldValue = originalValue,
NewValue = currentValue,
ModifiedByName = user.LastName + ", " + user.FirstName,
ModifiedById = UserId,
ModifiedBy = user,
ModifiedDate = DateTime.Now
};
ChangeLogs.Add(log);
}
}
}
return base.SaveChanges();
}
public class ChangeLog
{
public int Id { get; set; }
public string EntityName { get; set; }
public string PropertyName { get; set; }
public string PrimaryKeyValue { get; set; }
public string OldValue { get; set; }
public string NewValue { get; set; }
public string ModifiedByName { get; set; }
[ForeignKey("ModifiedBy")]
[DisplayName("Modified By")]
public string ModifiedById { get; set; }
public virtual User ModifiedBy { get; set; }
[Column(TypeName = "datetime2")]
public DateTime? ModifiedDate { get; set; }
}

Related

Ignore this[] property with Code First Fluent Api? Column 'Item' is added

Edit: full source code now available at https://bitbucket.org/moerie/multilanguagestring
I've implemented a class called MultilingualString which can serialize a dictionary to XML and be saved to the database.
(See source here: https://gist.github.com/4491990 )
This is actually an implementation of IDictionary but I don't actually add that to my class as it seems to cause hiccups for the Entity Framework.
usage of this class is like follows:
Suppose a class Product is defined like this:
public class Product
{
public int Id { get; set; }
public virtual MultilingualString Description { get; set; }
public virtual MultilingualString Name { get; set; }
}
Then you can do the following:
// make some products
var product1 = new Product
{
Id = 1,
Name = new MultilingualString(),
Description = new MultilingualString()
};
product1.Name["fr"] = "Produit 1";
product1.Description["fr"] = "Déscription 1";
product1.Name["nl"] = "Produkt 1";
product1.Description["nl"] = "Omschrijving 1";
Anyway, since I only want the Id and the Xml value I made an EntityTypeConfiguration:
public class MultilingualStringConfiguration: EntityTypeConfiguration<MultilingualString>
{
public MultilingualStringConfiguration()
{
Ignore(m => m.Count);
Ignore(m => m.Keys);
Ignore(m => m.Values);
Ignore(m => m.IsReadOnly);
}
}
When I run this code, the MultilingualStrings table is created as followed:
CREATE TABLE [dbo].[MultilingualStrings](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Value] [nvarchar](max) NULL,
[Item] [nvarchar](max) NULL,
CONSTRAINT [PK_dbo.MultilingualStrings] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
As you can see, the Entity Framework adds a property called 'Item', and I suspect this is because of the following property in MultilingualString:
/// <summary>
/// Gets or sets the element with the specified key.
/// </summary>
/// <returns>
/// The element with the specified key.
/// </returns>
/// <param name="key">The key of the element to get or set.</param><exception cref="T:System.ArgumentNullException"><paramref name="key"/> is null.</exception><exception cref="T:System.Collections.Generic.KeyNotFoundException">The property is retrieved and <paramref name="key"/> is not found.</exception><exception cref="T:System.NotSupportedException">The property is set and the <see cref="T:System.Collections.Generic.IDictionary`2"/> is read-only.</exception>
public string this[string key]
{
get
{
if (ContainsKey(key))
return Translations[key];
return string.Empty;
}
set
{
Translations[key] = value;
UpdateValue();
}
}
This is the property that allows my MultilingualString property to be used as a Dictionary, but sadly I can't seem to reach this property from the EntityTypeConfiguration.
How can I ignore this property? Thanks in advance.
P.S. For those of you interested in the MultilingualString class, note that you will need the LinqKit library (and specifically the AsExpandable() and Compile() functions to perform Linq queries with this class.
Do you have access to edit MultilingualString? Try adding the annotation [NotMapped] on top of the property that's giving you trouble.
Full source code:
https://bitbucket.org/moerie/multilanguagestring
Since this MultilingualString is in fact a property, I tried to resolve my problem by just mapping my MultilingualString to a String property. Then I could just tell the Entity Framework to not map my MultilingualString property entirely.
This is my product class now:
public class Product
{
private MultilingualString _description;
private MultilingualString _name;
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public virtual string DescriptionXml
{
get { return Description.Value; }
set { Description.Value = value; }
}
public virtual string NameXml
{
get { return Name.Value; }
set { Name.Value = value; }
}
[NotMapped]
public virtual MultilingualString Description
{
get { return _description ?? (_description = new MultilingualString()); }
set { _description = value; }
}
[NotMapped]
public virtual MultilingualString Name
{
get { return _name ?? (_name = new MultilingualString()); }
set { _name = value; }
}
}
This also seriously simplifies my database.
Yes, I couldn't find a fluent API solution for my original problem, but this workaround turned out to be excellent.

Entity Framework, Update The UPDATE statement conflicted with the FOREIGN KEY constraint "FK

I wrote a Silverlight MVVM (SimpleMVVM toolkit) Ria Services App with EntityFramework Model generated from exsisting DB.
At first ViewModel of Page which display list parent entities I load parents, after selecting one, I can further go to edition page, where I do menagent of child entities. Before that, I pass parent Entity by pageDataHelper - key value pair Dictionary, which it holds, to ViewModel of Edition Page, and refresh it from database.
Next, I can load by entity primary key, it's childs to ListBox. By this, i can do CRUD's of childs. Anfortunatly, when I want to update one one of them, I got:
The UPDATE statement conflicted with the FOREIGN KEY constraint
"FK_Pozycje_Kosztorysy" The conflict occurred in database
"D:...\APP_DATA\EMS.MDF", table "dbo.Kosztorysy", column 'KosID'.
Aplication makes updates in DB, but entityframework always throws exception, which I handle...
Both tables in db have set On Update/Delete Cascade.
Entities looks:
internal sealed class KosztorysMetadata
{
public int KosID { get; set; }//Primary Key
public EntityCollection<Pozycja> Pozycje { get; set; } //Childs
}
}
internal sealed class PozycjaMetadata
{
public int KosID { get; set; }// Foreign Key
public int PozID { get; set; }//Primary Key
}
}
In App.Web, functins to get entities:
public IQueryable<Kosztorys> GetKosztorysByID(int id)
{
var query = from k in this.ObjectContext.Kosztorysy
where k.KosID==id
select k;
return query;
}
public IQueryable<Pozycja> GetPozycjeGlowne(Int32 id)
{
var query = from k in this.ObjectContext.Pozycje
where k.KosID == id && (k.NadPozID == null || k.TypRMS == 0 || k.TypRMS == TypRMSBVals.Dzial)
select k;
return query;
}
In ViewModel;
class KosztorysViewModel{
public Kosztorys WybranyKosztorys
{
get { return wybranyKosztorys; }
set{ WybranyKosztorys = value;}}
private ObservableCollection<Kosztorys> kosztorysy;
public ObservableCollection<Kosztorys> Kosztorysy{}}
public void OdsiwezKosztorys()//Refresh parent
{ this.serviceAgent.PobKosztorys(WybranyKosztorys.KosID, (encje, ex) => { kosztorysOdswiezony(encje, blad); });
}
void kosztorysOdswiezony(List<Kosztorys> encje, Exception exc) //Refreshed Parent
{ Kosztorysy = new ObservableCollection<Kosztorys>(encje);
WybranyKosztorys = Kosztorysy[0];}
public void PobDzialy()
{
if (WybranyKosztorys != null)
{
this.serviceAgent.PobGlownePozycje(WybranyKosztorys.KosID, (encje, ex) => dzialyPobrane(encje, ex));
}
}
void dzialyPobrane(List<Pozycja> encje, Exception exc) //callback
{
Dzialy.Clear();
Dzialy = new ObservableCollection<Pozycja>(encje);
}}
What's wrong? I use ;EF4.3, Silverlight 5, MS SQL Server 2008R

EF5 Code First Enums and Lookup Tables

I'd like to define an enum for EF5 to use, and a corresponding lookup table. I know EF5 now supports enums, but out-of-the-box, it seems it only supports this at the object level, and does not by default add a table for these lookup values.
For example, I have a User entity:
public class User
{
int Id { get; set; }
string Name { get; set; }
UserType UserType { get; set; }
}
And a UserType enum:
public enum UserType
{
Member = 1,
Moderator = 2,
Administrator = 3
}
I would like for database generation to create a table, something like:
create table UserType
(
Id int,
Name nvarchar(max)
)
Is this possible?
Here's a nuget package I made earlier that generates lookup tables and applies foreign keys, and keeps the lookup table rows in sync with the enum:
https://www.nuget.org/packages/ef-enum-to-lookup
Add that to your project and call the Apply method.
Documentation on github: https://github.com/timabell/ef-enum-to-lookup
It is not directly possible. EF supports enums on the same level as .NET so enum value is just named integer => enum property in class is always integer column in the database. If you want to have table as well you need to create it manually in your own database initializer together with foreign key in User and fill it with enum values.
I made some proposal on user voice to allow more complex mappings. If you find it useful you can vote for the proposal.
I wrote a little helper class, that creates a database table for the enums specified in the UserEntities class. It also creates a foreign key on the tables that referencing the enum.
So here it is:
public class EntityHelper
{
public static void Seed(DbContext context)
{
var contextProperties = context.GetType().GetProperties();
List<PropertyInfo> enumSets = contextProperties.Where(p =>IsSubclassOfRawGeneric(typeof(EnumSet<>),p.PropertyType)).ToList();
foreach (var enumType in enumSets)
{
var referencingTpyes = GetReferencingTypes(enumType, contextProperties);
CreateEnumTable(enumType, referencingTpyes, context);
}
}
private static void CreateEnumTable(PropertyInfo enumProperty, List<PropertyInfo> referencingTypes, DbContext context)
{
var enumType = enumProperty.PropertyType.GetGenericArguments()[0];
//create table
var command = string.Format(
"CREATE TABLE {0} ([Id] [int] NOT NULL,[Value] [varchar](50) NOT NULL,CONSTRAINT pk_{0}_Id PRIMARY KEY (Id));", enumType.Name);
context.Database.ExecuteSqlCommand(command);
//insert value
foreach (var enumvalue in Enum.GetValues(enumType))
{
command = string.Format("INSERT INTO {0} VALUES({1},'{2}');", enumType.Name, (int)enumvalue,
enumvalue);
context.Database.ExecuteSqlCommand(command);
}
//foreign keys
foreach (var referencingType in referencingTypes)
{
var tableType = referencingType.PropertyType.GetGenericArguments()[0];
foreach (var propertyInfo in tableType.GetProperties())
{
if (propertyInfo.PropertyType == enumType)
{
var command2 = string.Format("ALTER TABLE {0} WITH CHECK ADD CONSTRAINT [FK_{0}_{1}] FOREIGN KEY({2}) REFERENCES {1}([Id])",
tableType.Name, enumProperty.Name, propertyInfo.Name
);
context.Database.ExecuteSqlCommand(command2);
}
}
}
}
private static List<PropertyInfo> GetReferencingTypes(PropertyInfo enumProperty, IEnumerable<PropertyInfo> contextProperties)
{
var result = new List<PropertyInfo>();
var enumType = enumProperty.PropertyType.GetGenericArguments()[0];
foreach (var contextProperty in contextProperties)
{
if (IsSubclassOfRawGeneric(typeof(DbSet<>), contextProperty.PropertyType))
{
var tableType = contextProperty.PropertyType.GetGenericArguments()[0];
foreach (var propertyInfo in tableType.GetProperties())
{
if (propertyInfo.PropertyType == enumType)
result.Add(contextProperty);
}
}
}
return result;
}
private static bool IsSubclassOfRawGeneric(Type generic, Type toCheck)
{
while (toCheck != null && toCheck != typeof(object))
{
var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck;
if (generic == cur)
{
return true;
}
toCheck = toCheck.BaseType;
}
return false;
}
public class EnumSet<T>
{
}
}
using the code:
public partial class UserEntities : DbContext{
public DbSet<User> User { get; set; }
public EntityHelper.EnumSet<UserType> UserType { get; set; }
public static void CreateDatabase(){
using (var db = new UserEntities()){
db.Database.CreateIfNotExists();
db.Database.Initialize(true);
EntityHelper.Seed(db);
}
}
}
I have created a package for it
https://www.nuget.org/packages/SSW.Data.EF.Enums/1.0.0
Use
EnumTableGenerator.Run("your object context", "assembly that contains enums");
"your object context" - is your EntityFramework DbContext
"assembly that contains enums" - an assembly that contains your enums
Call EnumTableGenerator.Run as part of your seed function. This will create tables in sql server for each Enum and populate it with correct data.
I have included this answer as I've made some additional changes from #HerrKater
I made a small addition to Herr Kater's Answer (also based on Tim Abell's comment). The update is to use a method to get the enum value from the DisplayName Attribute if exists else split the PascalCase enum value.
private static string GetDisplayValue(object value)
{
var fieldInfo = value.GetType().GetField(value.ToString());
var descriptionAttributes = fieldInfo.GetCustomAttributes(
typeof(DisplayAttribute), false) as DisplayAttribute[];
if (descriptionAttributes == null) return string.Empty;
return (descriptionAttributes.Length > 0)
? descriptionAttributes[0].Name
: System.Text.RegularExpressions.Regex.Replace(value.ToString(), "([a-z](?=[A-Z])|[A-Z](?=[A-Z][a-z]))", "$1 ");
}
Update Herr Katers example to call the method:
command = string.Format("INSERT INTO {0} VALUES({1},'{2}');", enumType.Name, (int)enumvalue,
GetDisplayValue(enumvalue));
Enum Example
public enum PaymentMethod
{
[Display(Name = "Credit Card")]
CreditCard = 1,
[Display(Name = "Direct Debit")]
DirectDebit = 2
}
you must customize your workflow of generation
1. Copy your default template of generation TablePerTypeStrategy
Location : \Microsoft Visual Studio 10.0\Common7\IDE\Extensions\Microsoft\Entity Framework Tools\DBGen.
2. Add custom activity who realize your need (Workflow Foundation)
3. Modify your section Database Generation Workflow in your project EF

How to use Entity Framework to map results of a stored procedure to entity with differently named parameters

I am trying to create a basic example using Entity Framework to do the mapping of the output of a SQL Server Stored procedure to an entity in C#, but the entity has differently (friendly) names parameters as opposed to the more cryptic names. I am also trying to do this with the Fluent (i.e. non edmx) syntax.
What works ....
The stored procedure returns values called: UT_ID, UT_LONG_NM, UT_STR_AD, UT_CITY_AD, UT_ST_AD, UT_ZIP_CD_AD, UT_CT
If I create an object like this ...
public class DBUnitEntity
{
public Int16 UT_ID { get; set; }
public string UT_LONG_NM { get; set; }
public string UT_STR_AD { get; set; }
public string UT_CITY_AD { get; set; }
public string UT_ST_AD { get; set; }
public Int32 UT_ZIP_CD_AD { get; set; }
public string UT_CT { get; set; }
}
and an EntityTypeConfiguration like this ...
public class DbUnitMapping: EntityTypeConfiguration<DBUnitEntity>
{
public DbUnitMapping()
{
HasKey(t => t.UT_ID);
}
}
... which I add in the OnModelCreating of the DbContext, then I can get the entities just fine out of the database, which is nice, using this ....
var allUnits = _context.Database.SqlQuery<DBUnitEntity>(StoredProcedureHelper.GetAllUnitsProc);
BUT, What Doesn't Work
If I want an entity like this, with friendlier names ....
public class UnitEntity : IUnit
{
public Int16 UnitId { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public Int32 Zip { get; set; }
public string Category { get; set; }
}
and an EntityTypeConfiguration like this ...
public UnitMapping()
{
HasKey(t => t.UnitId);
Property(t => t.UnitId).HasColumnName("UT_ID");
Property(t => t.Name).HasColumnName("UT_LONG_NM");
Property(t => t.Address).HasColumnName("UT_STR_AD");
Property(t => t.City).HasColumnName("UT_CITY_AD");
Property(t => t.State).HasColumnName("UT_ST_AD");
Property(t => t.Zip).HasColumnName("UT_ZIP_CD_AD");
Property(t => t.Category).HasColumnName("UT_CT");
}
When I try to get the data I get a System.Data.EntityCommandExecutionException with the message ....
"The data reader is incompatible with the specified 'DataAccess.EFCodeFirstSample.UnitEntity'. A member of the type, 'UnitId', does not have a corresponding column in the data reader with the same name."
If I add the "stored procedure named" property to the entity, it goes and complains about the next "unknown" property.
Does "HasColumnName" not work as I expect/want it to in this code-first stored procedure fluent style of EF?
Update:
Tried using DataAnnotations (Key from ComponentModel, and Column from EntityFramework) ... ala
public class UnitEntity : IUnit
{
[Key]
[Column("UT_ID")]
public Int16 UnitId { get; set; }
public string Name { get; set; }
That did remove the need for any EntityTypeConfiguration at all for the DBUnitEntity with the database-identical naming (i.e. just adding the [Key] Attribute), but did nothing for the entity with the property names that don't match the database (same error as before).
I don't mind using the ComponentModel Annotations in the Model, but I really don't want to use the EntityFramework Annotations in the model if I can help it (don't want to tie the Model to any specific data access framework)
From Entity Framework Code First book (page 155):
The SQLQuery method always attempts the column-to-property matching based on property name...
None that the column-to-property name matching does not take any mapping into account. For example, if you had mapped the DestinationId property to a column called Id in the Destination table, the SqlQuery method would not use this mapping.
So you cannot use mappings when calling stored procedure. One workaround is to modify your stored procedure to return result with aliases for each column that will match your object properties' names.
Select UT_STR_AD as Address From SomeTable etc
This isn't using Entity Framework but it is stemming from dbcontext. I have spent hours upon hours scouring the internet and using dot peek all for nothing. I read some where that the ColumnAttribute is ignored for SqlQueryRaw. But I have crafted up something with reflection, generics, sql datareader, and Activator. I am going to be testing it on a few other procs. If there is any other error checking that should go in, comment.
public static List<T> SqlQuery<T>( DbContext db, string sql, params object[] parameters)
{
List<T> Rows = new List<T>();
using (SqlConnection con = new SqlConnection(db.Database.Connection.ConnectionString))
{
using (SqlCommand cmd = new SqlCommand(sql, con))
{
cmd.CommandType = CommandType.StoredProcedure;
foreach (var param in parameters)
cmd.Parameters.Add(param);
con.Open();
using (SqlDataReader dr = cmd.ExecuteReader())
{
if (dr.HasRows)
{
var dictionary = typeof(T).GetProperties().ToDictionary(
field => CamelCaseToUnderscore(field.Name), field => field.Name);
while (dr.Read())
{
T tempObj = (T)Activator.CreateInstance(typeof(T));
foreach (var key in dictionary.Keys)
{
PropertyInfo propertyInfo = tempObj.GetType().GetProperty(dictionary[key], BindingFlags.Public | BindingFlags.Instance);
if (null != propertyInfo && propertyInfo.CanWrite)
propertyInfo.SetValue(tempObj, Convert.ChangeType(dr[key], propertyInfo.PropertyType), null);
}
Rows.Add(tempObj);
}
}
dr.Close();
}
}
}
return Rows;
}
private static string CamelCaseToUnderscore(string str)
{
return Regex.Replace(str, #"(?<!_)([A-Z])", "_$1").TrimStart('_').ToLower();
}
Also something to know is that all of our stored procs return lowercase underscore delimited. The CamelCaseToUnderscore is built specifically for it.
Now BigDeal can map to big_deal
You should be able to call it like so
Namespace.SqlQuery<YourObj>(db, "name_of_stored_proc", new SqlParameter("#param",value),,,,,,,);
The example posted by "DeadlyChambers" is great but I would like to extend the example to include the ColumnAttribute that you can use with EF to add to a properties to map a SQL field to a Class property.
Ex.
[Column("sqlFieldName")]
public string AdjustedName { get; set; }
Here is the modified code.
This code also include a parameter to allow for custom mappings if needed by passing a dictionary.
You will need a Type Converter other than Convert.ChangeType for things like nullable types.
Ex. If you have a field that is bit in the database and nullable boolean in .NET you will get a type convert issue.
/// <summary>
/// WARNING: EF does not use the ColumnAttribute when mapping from SqlQuery. So this is a "fix" that uses "lots" of REFLECTION
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="database"></param>
/// <param name="sqlCommandString"></param>
/// <param name="modelPropertyName_sqlPropertyName">Model Property Name and SQL Property Name</param>
/// <param name="sqlParameters">SQL Parameters</param>
/// <returns></returns>
public static List<T> SqlQueryMapped<T>(this System.Data.Entity.Database database,
string sqlCommandString,
Dictionary<string,string> modelPropertyName_sqlPropertyName,
params System.Data.SqlClient.SqlParameter[] sqlParameters)
{
List<T> listOfT = new List<T>();
using (var cmd = database.Connection.CreateCommand())
{
cmd.CommandText = sqlCommandString;
if (cmd.Connection.State != System.Data.ConnectionState.Open)
{
cmd.Connection.Open();
}
cmd.Parameters.AddRange(sqlParameters);
using (var dataReader = cmd.ExecuteReader())
{
if (dataReader.HasRows)
{
// HACK: you can't use extension methods without a type at design time. So this is a way to call an extension method through reflection.
var convertTo = typeof(GenericExtensions).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(mi => mi.Name == "ConvertTo").Where(m => m.GetParameters().Count() == 1).FirstOrDefault();
// now build a new list of the SQL properties to map
// NOTE: this method is used because GetOrdinal can throw an exception if column is not found by name
Dictionary<string, int> sqlPropertiesAttributes = new Dictionary<string, int>();
for (int index = 0; index < dataReader.FieldCount; index++)
{
sqlPropertiesAttributes.Add(dataReader.GetName(index), index);
}
while (dataReader.Read())
{
// create a new instance of T
T newT = (T)Activator.CreateInstance(typeof(T));
// get a list of the model properties
var modelProperties = newT.GetType().GetProperties();
// now map the SQL property to the EF property
foreach (var propertyInfo in modelProperties)
{
if (propertyInfo != null && propertyInfo.CanWrite)
{
// determine if the given model property has a different map then the one based on the column attribute
string sqlPropertyToMap = (propertyInfo.GetCustomAttribute<ColumnAttribute>()?.Name ?? propertyInfo.Name);
string sqlPropertyName;
if (modelPropertyName_sqlPropertyName!= null && modelPropertyName_sqlPropertyName.TryGetValue(propertyInfo.Name, out sqlPropertyName))
{
sqlPropertyToMap = sqlPropertyName;
}
// find the SQL value based on the column name or the property name
int columnIndex;
if (sqlPropertiesAttributes.TryGetValue(sqlPropertyToMap, out columnIndex))
{
var sqlValue = dataReader.GetValue(columnIndex);
// ignore this property if it is DBNull
if (Convert.IsDBNull(sqlValue))
{
continue;
}
// HACK: you can't use extension methods without a type at design time. So this is a way to call an extension method through reflection.
var newValue = convertTo.MakeGenericMethod(propertyInfo.PropertyType).Invoke(null, new object[] { sqlValue });
propertyInfo.SetValue(newT, newValue);
}
}
}
listOfT.Add(newT);
}
}
}
}
return listOfT;
}

Entity Framework 4.1 - Database First approach - How to find column names for primary key fields?

I'm using EF4.1 for DAL for an application, and I use DbContext template generator with POCP entities. The model is created from the database, so all the fields / PK's / FK's / relations are already defined in database.
I need to find out in code which are the fields for the table for an entity.
Some tables might have a single field PK, while other might have compound PK. Whan I need is to have a method that will return me a List for an entity, with all field names composing the primary keys. It can be from DbContext, or from entity, doesn't matter.
I could even customize the template to generate a method in POCO entity, as below:
public List<string> PrimaryKey()
{
List<string> pk = new List<string>();
pk.AddRange(
new string[] {"Field1", "Field2"});
return pk;
}
but I don't know how to find the field names composing the PK.
Any suggestions?
Thank you
I did some research and I modified the template to generate a property that returns this for me.
First I customized the template to generate strong typed names for field names (I hate using strings in code which can cause problems when refactoring). Then that is used to generate a property that returns primary key fields as List
Here are the changes to template (I used ADO.NET DbContext Template Generator, but for any other template it should be very similar):
<#=Accessibility.ForType(entity)#>
<#=code.SpaceAfter(code.AbstractOption(entity))#>partial class <#=code.Escape(entity)#>
<#=code.StringBefore(" : ", code.Escape(entity.BaseType))#>
{
<#
WriteStrongTypedPropertyNames(code, entity); // <-- Insert this
WritePrimaryKeyProperty(code, entity); // <-- and this
// .....
And at the end of template file add:
<#+
void WriteStrongTypedPropertyNames(CodeGenerationTools code, EntityType entity)
{
#> /// <summary>
/// Strong typed property names
/// </summary>
public class PropertyNames
{
<#+
foreach (var property in entity.Properties)
{
#>
public const string <#=code.Escape(property)#> = "<#=property#>";
<#+
}
#>
}
<#+
}
void WritePrimaryKeyProperty(CodeGenerationTools code, EntityType entity)
{
#> /// <summary>
/// Returns primary key as List
/// </summary>
public List<string> PrimaryKey
{
get
{
List<string> pk = new List<string>();
pk.AddRange(
new string[] {
<#+
foreach (var member in entity.KeyMembers)
{
string delim = "";
#>
<#=delim#> PropertyNames.<#=code.Escape(member.Name)#>
<#+
delim=",";
}
#> });
return pk;
}
}
<#+
}
#>
It generates a code as below in the entity:
/// <summary>
/// Strong typed property names
/// </summary>
public class PropertyNames
{
public const string AppID = "AppID";
public const string AppName = "AppName";
public const string AppCode = "AppCode";
}
/// <summary>
/// Returns primary key as List
/// </summary>
public List<string> PrimaryKey
{
get
{
List<string> pk = new List<string>();
pk.AddRange(
new string[] {
PropertyNames.AppID
});
return pk;
}
}
Hope this helps someone
I had a tough time trying to do almost the same thing, getting the primary key name and value at runtime when the type is unknown, from a DbContext. I was just get trying to implement an auditing scheme for deletes, and every solution i find involves superfluous code that I dont really understand. The EntityKey is not available from a DbContext, which is also confusing and annoying. The last 5 lines may save you 5 hours and 1 yr of baldness. I am not attempting this for Inserts, so if you do, you need to inspect those values carefully as they may be 0 or null.
foreach(var entry in ChangeTracker.Entries<IAuditable>()) {
...
case EntityState.Deleted:
...
var oc = ((IObjectContextAdapter.this).ObjectContext; //.this is a DbContext EntityKey
ek = oc.ObjectStateManager.GetObjectStateEntry(entry.Entity).EntityKey;
var tablename= ek.EntitySetName;
var primaryKeyField = ek.EntityKeyValues[0].Key; //assumes only 1 primary key var
primaryKeyValue = ek.EntityKeyValues[0].Value;
stumbled across this post after having done something similar to #bzamfir. I thought I would post what I did in hopes that it might alleviate some of the headaches that #Bill mentions that I have also experienced!
Using the DBContext, I also modified the Template.
1) Add Imports System.ComponentModel.DataAnnotations to the list of Imports already in the file
2) Add code to the Primitive Properties For Each Loop which adds the KeyAttribute to the primary key properties. It should look like:
For Each edmProperty As EdmProperty In primitiveProperties
If ef.IsKey(edmProperty) Then
#><#= code.StringBefore("<KeyAttribute()>",Environment.NewLine & CodeRegion.GetIndent(region.CurrentIndentLevel + 2))#><#
End If
WriteProperty(code, edmProperty)
Next
You can add this Extension Method to your code somewhere which will find the Primary Key based off of the KeyAttribute.
<Extension()>
Public Function FindPrimaryKeyProperty(Of T As Class)(context As DbContext, TEntity As T) As PropertyInfo
Return TEntity.GetType().GetProperties().Single(Function(p) p.GetCustomAttributes(GetType(KeyAttribute), True).Count > 0)
End Function
I do recognize the point that this function will fail if you have more than one property flagged with KeyAttribute, however, for my situation this was not the case.
Alternatively, I just came across this solution that appears to work just fine and doesn't require any Template editing (+1).
http://blog.oneunicorn.com/2012/05/03/the-key-to-addorupdate/
public static IEnumerable<string> KeysFor(this DbContext context, Type entityType)
{
Contract.Requires(context != null);
Contract.Requires(entityType != null);
entityType = ObjectContext.GetObjectType(entityType);
var metadataWorkspace =
((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace;
var objectItemCollection =
(ObjectItemCollection)metadataWorkspace.GetItemCollection(DataSpace.OSpace);
var ospaceType = metadataWorkspace
.GetItems<EntityType>(DataSpace.OSpace)
.SingleOrDefault(t => objectItemCollection.GetClrType(t) == entityType);
if (ospaceType == null)
{
throw new ArgumentException(
string.Format(
"The type '{0}' is not mapped as an entity type.",
entityType.Name),
"entityType");
}
return ospaceType.KeyMembers.Select(k => k.Name);
}