class DemoUser
{
[TitleCase]
public string FirstName { get; set; }
[TitleCase]
public string LastName { get; set; }
[UpperCase]
public string Salutation { get; set; }
[LowerCase]
public string Email { get; set; }
}
Suppose i have demo-class as written above, i want to create some custom annotations like LowerCase,UpperCase etc so that its value gets converted automatically. Doing this will enable me to use these annotations in other classes too.
As Ladislav implied, this is two questions in one.
Assuming you follow the recipe for creating attributes in Jefim's link, and assuming you're calling those created attribute classes "UpperCaseAttribute", "LowerCaseAttribute", and "TitleCaseAttribute", the following SaveChanges() override should work in EF 4.3 (the current version as of the time of this answer post).
public override int SaveChanges()
{
IEnumerable<DbEntityEntry> changedEntities = ChangeTracker.Entries().Where(e => e.State == System.Data.EntityState.Added || e.State == System.Data.EntityState.Modified);
TextInfo textInfo = Thread.CurrentThread.CurrentCulture.TextInfo;
changedEntities.ToList().ForEach(entry =>
{
var properties = from attributedProperty in entry.Entity.GetType().GetProperties()
where attributedProperty.PropertyType == typeof (string)
select new { entry, attributedProperty,
attributes = attributedProperty.GetCustomAttributes(true)
.Where(attribute => attribute is UpperCaseAttribute || attribute is LowerCaseAttribute || attribute is TitleCaseAttribute)
};
properties = properties.Where(p => p.attributes.Count() > 1);
properties.ToList().ForEach(p =>
{
p.attributes.ToList().ForEach(att =>
{
if (att is UpperCaseAttribute)
{
p.entry.CurrentValues[p.attributedProperty.Name] = textInfo.ToUpper(((string)p.entry.CurrentValues[p.attributedProperty.Name]));
}
if (att is LowerCaseAttribute)
{
p.entry.CurrentValues[p.attributedProperty.Name] = textInfo.ToLower(((string)p.entry.CurrentValues[p.attributedProperty.Name]));
}
if (att is TitleCaseAttribute)
{
p.entry.CurrentValues[p.attributedProperty.Name] = textInfo.ToTitleCase(((string)p.entry.CurrentValues[p.attributedProperty.Name]));
}
});
});
});
return base.SaveChanges();
}
You can override the SaveChanges method in your EF context (if you use default code-generation just write a partial class). Something like the following:
public partial class MyEntityContext
{
public override int SaveChanges(SaveOptions options)
{
IEnumerable<ObjectStateEntry> changedEntities =
this.ObjectStateManager.GetObjectStateEntries(
System.Data.EntityState.Added | System.Data.EntityState.Modified);
// here you can loop over your added/changed entities and
// process the custom attributes that you have
return base.SaveChanges(options);
}
}
Related
I'm trying to get the Audit:NET EntityFramework.Core extension to write an AuditLog entry per changed property.
For this purpose I've overidden the EntityFrameworkDataProvider.InsertEvent with a custom DataProvider.
The problem is, using DbContextHelper.Core.CreateAuditEvent to create a new EntityFrameworkEvent returns null.
The reason seems to be, at this point in the code execution DbContextHelper.GetModifiedEntries determines all EF Entries have State.Unmodified, even if they are clearly included in the EventEntry changes.
I'm trying to circumvent CreateAuditEvent by manually creating the contents is impossible due to private/internal properties.
Maybe there is an alternative solution to this problem I'm not seeing, i'm open to all suggestions.
Audit entity class
public class AuditLog
{
public int Id { get; set; }
public string Description { get; set; }
public string OldValue { get; set; }
public string NewValue { get; set; }
public string PropertyName { get; set; }
public DateTime AuditDateTime { get; set; }
public Guid? AuditIssuerUserId { get; set; }
public string AuditAction { get; set; }
public string TableName { get; set; }
public int TablePK { get; set; }
}
Startup configuration
Audit.Core.Configuration.Setup()
.UseCustomProvider(new CustomEntityFrameworkDataProvider(x => x
.AuditEntityAction<AuditLog>((ev, ent, auditEntity) =>
{
auditEntity.AuditDateTime = DateTime.Now;
auditEntity.AuditAction = ent.Action;
foreach(var change in ent.Changes)
{
auditEntity.OldValue = change.OriginalValue.ToString();
auditEntity.NewValue = change.NewValue.ToString();
auditEntity.PropertyName = change.ColumnName;
}
}
Custom data provider class
public class CustomEntityFrameworkDataProvider : EntityFrameworkDataProvider
{
public override object InsertEvent(AuditEvent auditEvent)
{
var auditEventEf = auditEvent as AuditEventEntityFramework;
if (auditEventEf == null)
return null;
object result = null;
foreach (var entry in auditEventEf.EntityFrameworkEvent.Entries)
{
if (entry.Changes == null || entry.Changes.Count == 0)
continue;
foreach (var change in entry.Changes)
{
var contextHelper = new DbContextHelper();
var newEfEvent = contextHelper.CreateAuditEvent((IAuditDbContext)auditEventEf.EntityFrameworkEvent.GetDbContext());
if (newEfEvent == null)
continue;
newEfEvent.Entries = new List<EventEntry>() { entry };
entry.Changes = new List<EventEntryChange> { change };
auditEventEf.EntityFrameworkEvent = newEfEvent;
result = base.InsertEvent(auditEvent);
}
}
return result;
}
}
Check my answer here https://github.com/thepirat000/Audit.NET/issues/323#issuecomment-673007204
You don't need to call CreateAuditEvent() you should be able to iterate over the Changes list on the original event and call base.InsertEvent() for each change, like this:
public override object InsertEvent(AuditEvent auditEvent)
{
var auditEventEf = auditEvent as AuditEventEntityFramework;
if (auditEventEf == null)
return null;
object result = null;
foreach (var entry in auditEventEf.EntityFrameworkEvent.Entries)
{
if (entry.Changes == null || entry.Changes.Count == 0)
continue;
// Call base.InsertEvent for each change
var originalChanges = entry.Changes;
foreach (var change in originalChanges)
{
entry.Changes = new List<EventEntryChange>() { change };
result = base.InsertEvent(auditEvent);
}
entry.Changes = originalChanges;
}
return result;
}
Notes:
This could impact performance, since it will trigger an insert to the database for each column change.
If you plan to use async calls to DbContext.SaveChangesAsync, you should also implement the InsertEventAsync method on your CustomDataProvider
The Changes property is only available for Updates, so if you also want to audit Inserts and Deletes, you'll need to add the logic to get the column values from the ColumnValues property on the event
I have an EF6 setup against a sql server db with about 60 tables in it.
I have entities for each table. What i'm trying to do is run the same method against a set of these entities that will be known at runtime.
The method is a qa/qc routine that does some data check on particular fields that are assured to be in each table.
I guess what i want to do is make the entity a parameter to the method so i can call it consecutive times.
I would also want to make a set of entities to pass as the parameter.
something like this:
List<string> entList = new List<string>(){"Table1","Table2","Table3"};
foreach (entName in entList)
{
//create an entity with the string name
//call myQAQCMethod with the entity
}
MyQAQCMethod (entity SomeEntity)
{
//run against this entity
doQAQC(SomeEntity);
}
Can this be done? Is it a job for reflection?
EDIT
using (var context = new Context())
{
var results = context.EntityAs.Where(a => a.Prop1 == e.Prop1)
.Where(a => a.Prop2 == e.Prop2)
.Select(a => new
{
APropertyICareAbout = a.Prop1,
AnotherPropertyICareAbout = a.Prop2
}).ToArray();
}
is precisely want i want to do. The thing is I want to avoid typing this loop 60 times. I think i'm looking for a way to "feed" a set of entities to this single method.
Also, thank you very much for helping me. I'm learning a lot.
You need to abstract an interface (entity framework won't even notice):
interface IQaQcable
{
int CommonInt { get; set; }
string CommonString { get; set; }
}
public class EntityA : IQaQcable
{
public int Id { get; set; }
public int CommonInt { get; set; }
public string CommonString { get; set; }
// other properties and relations
}
public class EntityB : IQaQcable
{
public int Id { get; set; }
public int CommonInt { get; set; }
public string CommonString { get; set; }
// other properties and relations
}
// in some unknown utility class
void MyQaQcMethod<T>(T entity) where T : IQaQcable
{
doSomethingWithIQaQcableProperties(entity.CommonInt, entity.CommonString);
}
// in some unknown test class
void Test()
{
var entities = new List<IQaQcable> { new EntityA(), new EntityB() };
foreach (var e in entities)
MyQaQcMethod(e);
}
Now, you could extract a base class from which each derives that actually implements the CommonInt and CommonString properties for each entity needing them, but that can get kind of tricky with Table-Per-Type/Table-Per-Hierarchy, so I'd start with this, and then consider introducing either an abstract or concrete base class as an improvement.
EDIT
Maybe your looking for something simpler than I first thought, based on your last comment.
Let's give ourselves what the DbContext for this might look like:
class Context : DbContext
{
public virtual DbSet<EntityA> EntityAs { get; set; }
public virtual DbSet<EntityB> EntityBs { get; set; }
}
So, it could just be that you wish to do this:
using (var context = new Context())
{
var results = context.EntityAs.Where(a => a.Prop1 == e.Prop1)
.Where(a => a.Prop2 == e.Prop2)
.Select(a => new
{
APropertyICareAbout = a.Prop1,
AnotherPropertyICareAbout = a.Prop2
}).ToArray();
}
Keeping in mind, if there is some set of properties in common across entity classes, you could still do something like the following:
IEnumerable<T> MyQaQcMethod(IQueryable<T> entities, T referenceEntity) where T : IQaQcAble
{
return entities.Where(e => SomePredicate(e, referenceEntity));
}
void Test()
{
using (var context = new Context())
{
// EntityA implements IQaQcAble
var resultsForA = MyQaQcMethod(context.EntityAs, defaultEntity).ToArray();
// so does EntityB, so can call with either
var resultsForB = MyQaQcMethod(context.EntityBs, defaultEntity).ToArray();
}
}
Keep in mind, to avoid modifying the generated entity classes, you could implement the interface members — and the interface — in a separate source file using partial classes. E.g.
// IQaQcAble.cs
internal interface IQaQcAble
{
int CommonInt { get; set; }
string CommonString { get; set; }
}
// a class whose existing property names match the interface
public partial class EntityA : IQaQcAble
{
int IQaQcAble.CommonInt
{
get { return CommonInt; }
set { CommonInt = value; }
}
string IQaQcAble.CommonString
{
get { return CommonString; }
set { CommonString = value; }
}
}
// a class whose property names differ
public partial class EntityB : IQaQcAble
{
int IQaQcAble.CommonInt
{
get { return SomeOtherInt; }
set { SomeOtherInt = value; }
}
string IQaQcAble.CommonString
{
get { return SomeOtherInt.ToString(); }
set { SomeOtherInt = Convert.ToInt32(value); }
}
}
I am new to EF and i have the following scenario:
I want to load the IHave apples without loading the whole Apples collection.
The model:
public class Category
{
public virtual ICollection<Classified> Apples { get; set; }
[NotMapped]
public bool IHaveApples {get { return Apples.count > 0; } }
}
And the FLUENT API Config:
public class CategoryConfiguration : EntityTypeConfiguration<Category>
{
public CategoryConfiguration()
{
HasMany(o => o.Apples ).WithRequired().HasForeignKey(o => o.CategoryId);
}
}
And in my controller i go with
//The controller returns IQueryable<Category>
var category = _contextProvider.Context.Categories;
Thanks in advance,
Stelios K.
As IHaveApples is a bool, you should simply use Apples.Any() instead of Count.
EDIT:
If you want this to be automatically set (i.e. the boolean is set without even accessing manually to your collection), what you could do is add an handler to the ObjectMaterialized event:
public class Category
{
...
public bool IHaveApples { get; set; }
}
((IObjectContextAdapter)yourDbContext).ObjectContext.ObjectMaterialized += (sender, e) =>
{
var entityAsCategory = e.Entity as Category;
if (entityAsCategory != null)
{
entityAsCategory.IHaveApples = yourDbContext
.Entry(entityAsCategory)
.Collection(z => z.Apples)
.Query()
.Any();
}
};
I'm using EF 4.3.1 and I'm doing my first implementation of Code First and testing the data. Here's my setup trying to implement eager loading.
public class Model
{
public int Id { get; set; }
public ICollection<ModelArchive> ModelArchives { get; set; }
}
public class ModelArchive
{
public int Id { get; set; }
public ICollection<Option> Options { get; set; }
}
public class Option
{
public int Id { get; set; }
public bool Deleted { get; set; }
}
I'd like to be able to only select the Options where Deleted == false in my query. So far I'm coming up empty or it results in an exception when running the query.
Here's my current query:
using (var db = new ModelContainer())
{
db.Configuration.LazyLoadingEnabled = false;
var model = db.Models.Where(m => m.Id == 3)
.Include(m => m.ModelArchives.Select(o => o.Option).Where(o => o.Deleted == false));
}
Exception: Message = "The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.\r\nParameter name: path"
Any help would be appreciated.
You can't load filtered data with Entity Framework. Navigation properties either contain all related entities, or none of them.
Consider to do manual join and return anonymous objects with not deleted options.
You can try
using (var db = new ModelContainer())
{
//db.Configuration.LazyLoadingEnabled = false;
var model = db.Models.Where(m => m.Id == 3 && m.ModelArchives.Option.Deleted==false)
.Include(m => m.ModelArchives.Option);
}
You can use generic function to get the data
public List<T> IncludeMultipleWithWhere(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includes)
{
IQueryable<T> itemWithIncludes = dbContext.Set<T>() as IQueryable<T>;
try
{
if (includes != null)
{
itemWithIncludes = includes.Aggregate(itemWithIncludes,
(current, include) => current.Include(include)).Where(predicate);
}
}
catch (Exception ex)
{
}
finally { }
return itemWithIncludes.ToList();
}
your calling function just need to passing the parameter like
Expression<Func<Models, bool>> whereCond1 = (m) => m.Id == 3 && m.ModelArchives.Option.Deleted==false;
Expression<Func<Models, object>>[] includeMulti = { m => m.ModelArchives.Option };
You were correct in disabling lazyloading.
But then you have to filter the navigation property in a subquery, and EF will magically attach it to your main query. Here is something that should work
using (var db = new ModelContainer())
{
db.Configuration.LazyLoadingEnabled = false;
var filteredModelArchives = db.ModelArchives.Select(o => o.Option).Where(o => o.Deleted == false).Include("Options");
var model = db.Models.Where(m => m.Id == 3);
}
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.