Entity Framework override SaveChanges Error - c#

"The property 'CreateDate' on type 'Commodity' is not a primitive or
complex property. The Property method can only be used with primitive
or complex properties. Use the Reference or Collection method."
This is the POCO class I am using and this code snippets for the context
public class OrdNumber
{
public int OrdNumberId { get; set; }
public string orderNum { get; set; }
// [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime CreateDate { get; set; }
}
public override int SaveChanges()
{
DateTime saveTime = DateTime.Now;
foreach (var entry in this.ChangeTracker.Entries().Where(e => e.State == System.Data.EntityState.Added))
{
if (entry.Property("CreateDate").CurrentValue == null)
entry.Property("CreateDate").CurrentValue = saveTime;
}
return base.SaveChanges();
}

You do not have to have the field on every table. You may not want audit tracking on all tables in that event you can inherit from a base with the audit info and override save changes accordingly. In this case inherit from AuditInfo
public class AuditInfo
{
[Required]
public DateTime CreatedDateTimeUtc { get; set; }
[Required]
public DateTime ModifiedDateTimeUtc { get; set; }
}
public override int SaveChanges()
{
var addedEntityList = ChangeTracker.Entries().Where(x => x.Entity is AuditInfo && x.State == EntityState.Added).ToList();
var updatedEntityList = ChangeTracker.Entries().Where(x => x.Entity is AuditInfo && x.State == EntityState.Modified).ToList();
if (addedEntityList.Any() || updatedEntityList.Any())
{
var context = HttpContext.Current;
if (context == null)
{
throw new ArgumentException("Context not available");
}
foreach (var addedEntity in addedEntityList)
{
((AuditInfo)addedEntity.Entity).CreatedDateTimeUtc = DateTime.UtcNow;
((AuditInfo)addedEntity.Entity).ModifiedDateTimeUtc = DateTime.UtcNow;
}
foreach (var updatedEntity in updatedEntityList)
{
((AuditInfo)updatedEntity.Entity).ModifiedDateTimeUtc = DateTime.UtcNow;
}
}
return base.SaveChanges();
}

Related

Include all navigation properties with recurcive properties EF Core

I have tried to Eagerly load all the data related to an entity, but I still have a problem regarding the recusive properties like this one :
public class Node : BaseAbstractEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
[ForeignKey("TypeId")]
public virtual NodeType Type { get; set; }
public int? TypeId { get; set; }
[ForeignKey("ParentId")]
public virtual Node Parent { get; set; }
public int? ParentId { get; set; }
public ICollection<Node> Children { get; set; }
}
I am using it in this method :
public async Task<object> JustGetAsync(Type type, JObject value, DbContext context)
{
int id = 0;
if (value != null && value["id"] != null)
id = Int32.Parse(value["id"].ToString());
if (id != 0)
return await context.FindAsync(type, id);
var TypeSet = (IQueryable<object>) context.GetType()
.GetMethod("Set")
.MakeGenericMethod(type)
.Invoke(context, null);
return await TypeSet.Include(context.GetIncludePaths(type)).ToListAsync();
}
the get IncludePaths is a code that I found here enter link description here that helps to eager all the properties :
public static IQueryable Include(this IQueryable source, IEnumerable navigationPropertyPaths)
where T : class
{
return navigationPropertyPaths.Aggregate(source, (query, path) => query.Include(path));
}
public static IEnumerable<string> GetIncludePaths(this DbContext context, Type clrEntityType)
{
var entityType = context.Model.FindEntityType(clrEntityType);
var includedNavigations = new HashSet<INavigation>();
var stack = new Stack<IEnumerator<INavigation>>();
while (true)
{
var entityNavigations = new List<INavigation>();
foreach (var navigation in entityType.GetNavigations())
{
if (includedNavigations.Add(navigation))
entityNavigations.Add(navigation);
}
if (entityNavigations.Count == 0)
{
if (stack.Count > 0)
yield return string.Join(".", stack.Reverse().Select(e => e.Current.Name));
}
else
{
foreach (var navigation in entityNavigations)
{
var inverseNavigation = navigation.FindInverse();
if (inverseNavigation != null)
includedNavigations.Add(inverseNavigation);
}
stack.Push(entityNavigations.GetEnumerator());
}
while (stack.Count > 0 && !stack.Peek().MoveNext())
stack.Pop();
if (stack.Count == 0) break;
entityType = stack.Peek().Current.GetTargetType();
}
}

cannot convert from 'System.Collections.Generic.ICollection<x>' to 'x'

i need to add some data in OptionRoleTable:
public class OptionRole
{
public int Id { get; set; }
public int RoleId { get; set; }
public int OptionsId { get; set; }
public virtual Role Role { get; set; }
public virtual Options Options { get; set; }
}
and this is Options Tabel:
public partial class Options
{
public int Id { get; set; }
public string OptionName { get; set; }
public string RouteFunctionName { get; set; }
public string Icon { get; set; }
public virtual ICollection<OptionRole> OptionRoles { get; set; }
}
i must check data not exist in OptionRole , when i using this code for add data in OptionRole :
public async Task<Options> findOptionsId(int optionId)
{
return await _option.FirstOrDefaultAsync(x => x.Id == optionId);
}
public async Task<bool> AddorUpdateOptions(int optionId, IList<int> selectedRoleValue)
{
List<OptionVM> optionVMs = new List<OptionVM>();
List<int> currentOptionValue = new List<int>();
var optionRole = await findOptionsId(optionId);
if (optionRole == null)
{
return false;
}
foreach (var item in selectedRoleValue)
{
var findRole = await _roleManager.FindByIdAsync(item);
var findOPR = optionRole.OptionRoles.FirstOrDefault(x => x.OptionsId== optionId && x.RoleId==item);
if (findOPR != null)
{
currentOptionValue.Add(item);
}
}
if (selectedRoleValue == null)
{
selectedRoleValue = new List<int>();
}
var newOptionRole = selectedRoleValue.Except(currentOptionValue).ToList();
foreach (var opRole in newOptionRole)
{
var findRole = await _roleManager.FindByIdAsync(opRole);
if (findRole != null)
{
optionRole.OptionRoles.Add(new OptionRole
{
OptionsId = optionRole.Id,
RoleId = findRole.Id
});
}
}
var removeOptionRole = currentOptionValue.Except(selectedRoleValue).ToList();
foreach (var remove in removeOptionRole)
{
var findOptionRole = _optionRoles.FirstOrDefault(x => x.Id == remove);
if (findOptionRole != null)
{
optionRole.OptionRoles.Remove(findOptionRole);
}
}
return Update(optionRole.OptionRoles);
}
I must have pass a class type of Options when i using this code . it show me this Error :
Severity Code Description Project File Line Suppression State
Error CS1503 Argument 1: cannot convert from 'System.Collections.Generic.ICollection' to 'StoreFinal.Entities.Entities.Identity.OptionRole' StoreFinal.Services C:\Users\Mr-Programer\Desktop\New folder\StoreFinal\StoreFinal.Services\Contracts\Identity\Service\ApplicationOptionRoleManager.cs 97 Active
Error in this line : return Update(optionRole.OptionRoles);
whats the problem ? how can i solve this problem ?
Edit :
Update Method :
public virtual bool Update(T entity)
{
try
{
Entities.Attach(entity);
return true;
}
catch (Exception)
{
return false;
}
}
Look at the Update Method signature:
public virtual bool Update(T entity);
It accepts a param type T which should be One Entity - Why One Entity -- because Entities.Attach() accepts only 1 Object. While what you are passing to it is:
return Update(optionRole.OptionRoles);
Where OptionRoles is of type: ICollection<OptionRole> --
For understandings sake, Change it to
return Update(optionRole.OptionRoles[0]);
or
return Update(optionRole.OptionRoles.First());
And then share the result.

EF saving entity references

I have the following situation.
I'm building a course management MVC site where I have courses but also course iterations I call them as it happens sometime that a new student will join just for one iteration.
I have a model courseIterations which possess a ICollection of students. When starting the course I want to generate the next nine iterations with default students which subscribed to the course. The problem I have is that when I list the iterations the students where not saved. What am I doing wrong? Or is there a better approach in general?
public class CourseIteration : AuditableEntity<int>
{
public Course Course { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Date")]
public DateTime Date { get; set; }
public virtual ICollection<Student> Students { get; set; }
public virtual ICollection<Instructor> Instructors { get; set; }
}
My attempt to fill
private void FillIterations(Course course, int coursePeriod)
{
var defaultStudents = _studentRepository.GetAllByCourse(course.CourseID).ToList(); // load the existing Items
course.Iterations = new List<CourseIteration>();
for (int i = 0; i < coursePeriod; i++)
{
var item = new CourseIteration
{
Course = course,
Date = course.StartDate.AddDays(i * 7),
Instructors = course.Instructors,
Students = new List<Student>()
};
foreach (var student in defaultStudents)
{
item.Students.Add(student);
}
_courseIterationRepository.Add(item);
course.Iterations.Add(item);
_courseIterationRepository.Save();
_courseRepository.Save();
}
}
I use a Generic Repository
public abstract class GenericRepository : IGenericRepository
where T : BaseEntity
{
protected DbContext _entities;
protected readonly IDbSet _dbset;
public GenericRepository(DbContext context)
{
_entities = context;
_dbset = context.Set<T>();
}
public virtual IEnumerable<T> GetAll()
{
return _dbset.AsEnumerable<T>();
}
public IEnumerable<T> FindBy(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
{
IEnumerable<T> query = _dbset.Where(predicate).AsEnumerable();
return query;
}
public virtual T Add(T entity)
{
return _dbset.Add(entity);
}
public virtual T Attach(T entity)
{
return _dbset.Attach(entity);
}
public virtual T Delete(T entity)
{
return _dbset.Remove(entity);
}
public virtual void Edit(T entity)
{
_entities.Set<T>().Attach(entity);
_entities.Entry(entity).State = EntityState.Modified;
Save();
}
public virtual void Save()
{
_entities.SaveChanges();
}
Custom Save function could be the problem?
public override int SaveChanges()
{
var modifiedEntries = ChangeTracker.Entries()
.Where(x => x.Entity is IAuditableEntity
&& (x.State == System.Data.Entity.EntityState.Added || x.State == System.Data.Entity.EntityState.Modified));
foreach (var entry in modifiedEntries)
{
IAuditableEntity entity = entry.Entity as IAuditableEntity;
if (entity != null)
{
string identityName = Thread.CurrentPrincipal.Identity.Name;
DateTime now = DateTime.UtcNow;
if (entry.State == System.Data.Entity.EntityState.Added)
{
entity.CreatedBy = identityName;
entity.CreatedDate = now;
}
else
{
base.Entry(entity).Property(x => x.CreatedBy).IsModified = false;
base.Entry(entity).Property(x => x.CreatedDate).IsModified = false;
}
entity.UpdatedBy = identityName;
entity.UpdatedDate = now;
}
}
return base.SaveChanges();
}

using entity framework base model and calling fields on parent class

I'm trying to use a base class with my entity framework models...
I have the following baseclass:
public class BaseModel
{
[Key]
public int Id { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime UpdatedDate { get; set; }
public DateTime? ExpiryDate { get; set; }
public bool IsActive { get; set; }
public Guid CreatedBy { get; set; }
public Guid UpdatedBy { get; set; }
}
I then have a class that inherits from it:
public class Family : BaseModel
Basically i then want to be able to set these base fields using something like:
private void SetBaseData(ref BaseModel baseModel, Guid currentUserId)
{
if (baseModel.Id < 1)
{
baseModel.CreatedDate = _datetime.Now();
baseModel.CreatedBy = currentUserId;
baseModel.IsActive = true;
}
baseModel.UpdatedDate = _datetime.Now();
baseModel.UpdatedBy = currentUserId;
}
And then called like:
Models.Family efFamily = _mapper.Map(family);
SetBaseData(ref efFamily, family.CurrentUserId);
I'm getting this but I thought I;d be able to do this or am I completely going down the wrong route?
Error 27 Argument 1: cannot convert from 'ref FamilyOrganiser.Repository.EntityFramework.Models.Family' to 'ref FamilyOrganiser.Repository.EntityFramework.Models.BaseModel'
You could add SetBaseData method to your BaseModel class, then it would look like this:
public class BaseModel
{
// your code, properties, etc.
...
public void SetBaseData(Guid currentUserId)
{
if (this.Id < 1)
{
this.CreatedDate = _datetime.Now();
this.CreatedBy = currentUserId;
this.IsActive = true;
}
this.UpdatedDate = _datetime.Now();
this.UpdatedBy = currentUserId;
}
}
Then you can use it like this on all classes that inherit your BaseModel:
Models.Family efFamily = _mapper.Map(family);
efFamily.SetBaseData(family.CurrentUserId);
One possibility is to over ride the SaveChanges() function by creating a base DataContext class.
Doing it this way, you will never have to call any function after mapping, entity framework will do it for you and will only update the updateddt field if it exists in the table.
Here is what we did:
Create an interface IDataContext like this:
public interface IMyDataContext
{
DbConnection Connection { get; }
IDbSet<MyClass> MyClasses{ get; }
int SaveChanges();
}
and then create a partial class for the DataContext
public partial class MyDataContext : DbContext, IMyDataContext
{
static HealthDataContext()
{
Database.SetInitializer<HealthDataContext>(null);
}
public IDbSet<MyClass> MyClasses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new MyClassMap());
}
public override int SaveChanges()
{
var changeSet = ChangeTracker.Entries();
if (changeSet != null)
{
foreach (var entry in changeSet.Where(c => c.State == EntityState.Deleted || c.State == EntityState.Added || c.State == EntityState.Modified))
{
switch (entry.State)
{
case EntityState.Added:
if (entry.Entity.GetType().GetProperty("createddt") != null)
{
entry.Entity.GetType().GetProperty("createddt").SetValue(entry.Entity, new Health.Core.Helpers.RealClock().UtcNow);
}
break;
case EntityState.Deleted:
break;
case EntityState.Detached:
break;
case EntityState.Modified:
if (entry.Entity.GetType().GetProperty("updateddt") != null)
{
entry.Entity.GetType().GetProperty("updateddt").SetValue(entry.Entity, new Health.Core.Helpers.RealClock().UtcNow);
}
break;
case EntityState.Unchanged:
break;
default:
break;
}
}
}
return base.SaveChanges();
}
}
We are using Code First so I'm not sure if this will work in all scenarios.
You can do it but you need to pass in a BaseModel as the parameter has the ref modifier. If you don't, the compiler would have to box your variable, then ref is back to you, and you'd lose the value. Instead do this:
Family efFamily = new Family();
BaseModel m = (BaseModel)efFamily;
SetBaseData(ref m, new Guid());

EF Code First Configuration of shared Base Class --> Single Configuration File

I am using EF 5.0 Code First in a C# project. I have a base clase which the majority of my domain models derive from.
public abstract class AuditableModelBase
{
public Int32 CreatedByUserId { get; set; }
public DateTime CreatedDate { get; set; }
public virtual UserProfile CreatedByUserProfile { get; set; }
public Int32 UpdatedByUserId { get; set; }
public DateTime UpdatedDate { get; set; }
public virtual UserProfile UpdatedByUserProfile { get; set; }
public AuditableModelBase()
{
CreatedByUserId = 1;
CreatedDate = DateTime.UtcNow;
UpdatedByUserId = 1;
UpdatedDate = DateTime.UtcNow;
}
}
However, for every single entity I have to define the specific configurations to wire these relationships together.
// Relationships
this.HasRequired(amb => amb.CreatedByUserProfile).WithMany().HasForeignKey(amb => amb.CreatedByUserId).WillCascadeOnDelete(false);
this.HasRequired(amb => amb.UpdatedByUserProfile).WithMany().HasForeignKey(amb => amb.UpdatedByUserId).WillCascadeOnDelete(false);
I'm looking for a way to just declare one Configuration similar to the one directly above for the abstract base class instead of having to create an individual configuration file for each of my entities. I'd love to just have one file named "AuditableModelBaseMap.cs" which will have my configuration instead of "Entity1Map.cs", "Entity2Map.cs", "Entity3Map.cs", etc. especially since all of those files have the exact same code inside.
Any advice?
Thanks.
Try it like below but I didnt test it .However if I were you, I wouldnt design Audit tables this way
class AuditableModelBaseMap : EntityTypeConfiguration<AuditableModelBase>
{
public AuditableModelBaseMap ()
{
this.HasRequired(amb => amb.CreatedByUserProfile).WithMany().HasForeignKey(amb => amb.CreatedByUserId).WillCascadeOnDelete(false);
this.HasRequired(amb => amb.UpdatedByUserProfile).WithMany().HasForeignKey(amb => amb.UpdatedByUserId).WillCascadeOnDelete(false);
}
}
THIS IS MY WAY OF Doing AUDITING
public interface IEntity
{
int Id { get; set; }
}
public interface IAuditable : IEntity
{
string UpdatedBy { get; set; }
string CreatedBy { get; set; }
DateTime CreatedDate { get; set; }
DateTime UpdateDate { get; set; }
}
Now any entity which is auditable will implement this class your context will look the following
public class MYContext : DbContext, ILicensingContext
{
private readonly IAuditLogBuilder _auditLogBuilder;
public LicensingContext()
: this(new AuditLogBuilder())
{
}
private IDbSet<Device> Devices { get; set; }
private IDbSet<AuditLog> AuditLogs { get; set; }
public MyContext(IAuditLogBuilder auditLogBuilder)
{
_auditLogBuilder = auditLogBuilder;
}
/// <summary>
/// 1. Constructs the AuditLog objects from the context
/// 2. Calls SaveChanges to save the actual object modified
/// 3. It updates the Log objects constructed in step 1 to populate the IDs returned from the Db
/// 4. Saves the AuditLogs
/// </summary>
/// <returns></returns>
public override int SaveChanges()
{
var entries = ChangeTracker.Entries<IAuditable>().ToList();
_auditLogBuilder.UpdateAuditables(entries);
IEnumerable<AuditLog> auditLogEntities = _auditLogBuilder.ConstructAuditLogs(entries).ToList();
int countOfAffectedRecords = base.SaveChanges();
_auditLogBuilder.UpdateAuditLogs(auditLogEntities);
foreach (AuditLog auditLogEntity in auditLogEntities)
{
GetDbSet<AuditLog>().Add(auditLogEntity);
}
base.SaveChanges();
return countOfAffectedRecords;
}
public IDbSet<TEntity> GetDbSet<TEntity>() where TEntity : class
{
return Set<TEntity>();
}
}
public class AuditLogBuilder : IAuditLogBuilder
{
private string _username;
private string Username
{
get
{
if (HttpContext.Current != null && HttpContext.Current.User != null)
{
_username = HttpContext.Current.User.Identity.Name;
}
if (String.IsNullOrWhiteSpace(_username))
{
_username = "Service Consumer";
}
return _username;
}
}
public IEnumerable<AuditLog> ConstructAuditLogs(IEnumerable<DbEntityEntry<IAuditable>> auditableEntities)
{
var audits = new List<AuditLog>();
if (auditableEntities != null)
{
audits.AddRange(auditableEntities
.Where(
e =>
e.State == EntityState.Modified || e.State == EntityState.Added ||
e.State == EntityState.Deleted)
.SelectMany(GetAuditLogs));
}
return audits;
}
public void UpdateAuditLogs(IEnumerable<AuditLog> auditLogEntities)
{
foreach (AuditLog auditLog in auditLogEntities)
{
auditLog.RecordId = auditLog.Entity.Id;
auditLog.UpdatedBy = auditLog.Entity.UpdatedBy;
if (String.Equals(auditLog.PropertyName, "id", StringComparison.CurrentCultureIgnoreCase))
{
auditLog.NewValue = auditLog.Entity.Id.ToString(CultureInfo.CurrentCulture);
}
}
}
public void UpdateAuditables(IEnumerable<DbEntityEntry<IAuditable>> entries)
{
if (entries != null)
{
foreach (var entry in entries)
{
entry.Entity.UpdateDate = DateTime.UtcNow;
entry.Entity.UpdatedBy = Username;
if (entry.Entity.Id == 0)
{
entry.Entity.CreatedDate = DateTime.UtcNow;
entry.Entity.CreatedBy = Username;
}
}
}
}
private static IEnumerable<AuditLog> GetAuditLogs(DbEntityEntry<IAuditable> entry)
{
var audits = new List<AuditLog>();
string entityName = ObjectContext.GetObjectType(entry.Entity.GetType()).Name;
switch (entry.State)
{
case EntityState.Added:
audits.AddRange(entry.CurrentValues.PropertyNames.Select(propertyName =>
new AuditLog
{
EntityName = entityName,
CreateDate = DateTime.UtcNow,
NewValue =
entry.CurrentValues[
propertyName] != null
? entry.CurrentValues[
propertyName].ToString()
: String.Empty,
PreviousValue = String.Empty,
PropertyName = propertyName,
Entity = entry.Entity,
Action = Actions.Create.ToString()
}));
break;
case EntityState.Deleted:
audits.AddRange(entry.OriginalValues.PropertyNames.Select(propertyName =>
new AuditLog
{
EntityName = entityName,
CreateDate = DateTime.UtcNow,
NewValue = String.Empty,
PreviousValue =
entry.OriginalValues[
propertyName] != null
? entry.OriginalValues[
propertyName].ToString
()
: String.Empty,
PropertyName = propertyName,
Entity = entry.Entity,
Action = Actions.Delete.ToString()
}));
break;
case EntityState.Modified:
audits.AddRange(entry.OriginalValues.PropertyNames.
Where(
propertyName =>
!Equals(entry.OriginalValues[propertyName],
entry.CurrentValues[propertyName]))
.Select(propertyName =>
new AuditLog
{
EntityName = entityName,
CreateDate = DateTime.UtcNow,
NewValue =
entry.CurrentValues[propertyName] != null
? entry.CurrentValues[propertyName].ToString()
: String.Empty,
PreviousValue =
entry.OriginalValues[propertyName] != null
? entry.OriginalValues[propertyName].ToString()
: String.Empty,
PropertyName = propertyName,
Entity = entry.Entity,
Action = Actions.Update.ToString()
}));
break;
}
return audits;
}
}
Have you tried this?
public class AuditableModelBaseMap : EntityTypeConfiguration<AuditableModelBase>
{
public AuditableModelBaseMap()
{
this.HasRequired(amb => amb.CreatedByUserProfile).WithMany().HasForeignKey(amb => amb.CreatedByUserId).WillCascadeOnDelete(false);
this.HasRequired(amb => amb.UpdatedByUserProfile).WithMany().HasForeignKey(amb => amb.UpdatedByUserId).WillCascadeOnDelete(false);
}
}

Categories