Entity Framework - generic auditing by entity or table - c#

I need to audit data in some tables in my server database so my clients can take partial updates (by table/entity). Data in the server database is only edited from the server website.
The audits will be requested by a client: WHERE Id > [Clients Last Id], the server will then do some processing and then return the latest audits to keep themselves up to date.
I can't seem to get to a generic pattern that will work across the board for all of my models:
public class Domain {
public int Id { get; set; }
public string Property1 { get; set; }
public int Property2 { get; set; }
}
Then I think I want to be able to do something like so:
public class DomainContext : DbContext {
public DbSet<Domain> Domain { get; set; }
public DbSet<History<Domain>> DomainHistory { get; set; }
}
This is my problem class taking this route, I want to inherit from Domain so things like property changes and EF migrations (in code first) will 'just work'. But I Cannot derive from 'T' because it is a type parameter
public class History<T>
: T //Cannot derive from 'T' because it is a type parameter
where T : class {
public DateTime CreatedOn { get; set; }
public int CreatedByUserId { get; set; }
public int EntityFK { get; set; }
// This will always be the current version
//public T Entity { get; set; }
// I could store a snapshot of the state at the time of the audit
public string XMLData { get; set; }
}
I don't know if my use of generics is warranted here but I'm basically trying to get to a point where I can do the below so my models play nicely with EF Migrations:
Domain d = GetDomainModel();
History<Domain> dh = new History<Domain>();
dh.Property1 = d.Property1;
dh.Property2 = d.Property2;
How can this be done?

For a basic audit of your entities you can use a base class and intercept the changue type in a override of savechangues in context, like this (sorry of format, i write in mobile):
public class AuditBase
{
//Adapt your requirements, the propertys are exists in db
public datetime creationdate { get; set; }
public datetime modificationdate { get; set; }
public string creationuser { get; set; }
public string modificationuser { get; set; }
}
public class ModelBBDD : AuditBase
{ }
You can override the SaveChanges method of Context. In the method, you can through the ChangueTracker property of Database Class for added or updates dto's, like this:
var dto = entity as auditbase;
if (dto == null) continue;
if (dto.state == entitystate.added)
{
((Auditbase)entity).creationdate = datetime.now;
((Auditbase)entity).creationuser = environment.username;
}
else if( dto.state == entitystate.modified)
...
...
If you can log all the changues of properties, you can trough all the properties of dto with reflection and type in SaveChanges, and save values in log.

Related

In Entity Framework, How to modify a list of objects stored in another class using System.Data.Entity.EntityState.Modified?

I want to update a List of Custom Objects stored in a class in entity Framework, But I'm running into this error: the entity type list`1 is not part of the model for the current context. I have observed what the problem is, but I don't have enough experience to solve this problem. Refer below code to get a better understanding of the issue at hand.
public class Appointment
{
public int AppointmentID { get; set; }
public int PetID { get; set; }
public int DoctorID { get; set; }
public DateTime AppointmentDate { get; set; }
public Status AppointmentStatus { get; set; }
public virtual List<ObservedPetIssue> ObservedPetIssueID { get; set; }
public string Reason { get; set; }
public virtual List<PrescribedMedicine> Prescription { get; set; }
public virtual List<DiagnosedSymptom> DiagnosedSymptomID { get; set; }
public virtual Vital VitalID { get; set; }
public virtual List<PrescribedTest> PrescribedTestID { get; set; }
public virtual List<Recommendation> RecommendationID { get; set; }
}
This is the class which has a list of other classes and one property which has a single class(Vital).
Here is the observation:
1)If I edit any field like PetID, DoctorID, or AppointmentStatus and write db.Entry(appt).CurrentValues.SetValues(editedAppointment); The changes are saved
If I edit any field inside VitalID and write db.Entry(appt.VitalID).CurrentValues.SetValues(editedAppointment.VitalID); The changes are saved
If I add any PrescribedMedicine to the Prescription list, or modify the existing PrescribedMedicine or not modify anything at all and write db.Entry(appt.PrescribedTestID).CurrentValues.SetValues(editedAppointment.PrescribedTestID); and error is throw stating: the entity type list`1 is not part of the model for the current context
I've Tried db.Entry(appt.Prescription).State = EntityState.Modified, still no success.
That you do is similar that db.Entry(new List<PrescribedTest>()). This fail because the db context don't know the entity type List<PrescribedTest>.
This work with VitalID because it's similar that db.Entry(new Vital()) and that success because the db context know the entity type Vital.
If you want update a full entity collection, you can :
context.Entry(appointment).Collection(a => a.PrescribedTestID).CurrentValue = editedAppointment.PrescribedTestID;

Inheriting EF class throws foreign key error in project using multiple data contexts

I apologize on the title as I'm not sure how to summarize the issue we are having in one sentence.
We have two solutions, one of our old code base, another with our latest and greatest, with both solutions sharing a project of code first EF models. The old solution also has another project of older EF models and its own database context. The old code worked with this blend of old and new until I added new classes that inherited an EF one. In our new system, CRUD operations work as expected with the sub classes, but on our old system we get this error:
The entity types 'DiscountActivity' and 'DiscountSeries' cannot share table 'Discounts' because they are not in the same type hierarchy or do not have a valid one to one foreign key relationship with matching primary keys between them.
Here are the classes in question:
[Table("Discounts")]
public class Discount
{
[Column("Id"), Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Column("Type")]
public string Type { get; set; }
[Column("Amount")]
public double Amount { get; set; }
}
public class DiscountActivity : Discount
{
[Column("TypeId")]
public int ActivityId { get; set; }
public virtual Activity Activity { get; set; }
public DiscountActivity()
{
Type = "Activity";
}
}
public class DiscountSeries : Discount
{
[Column("TypeId")]
public int SeriesId { get; set; }
public virtual Series Series { get; set; }
public DiscountSeries()
{
Type = "Series";
}
}
[Table("Activities")]
public class Activity
{
[Column("Id"), Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Column("Name")]
public string Name { get; set; }
public virtual ICollection<DiscountActivity> Discounts { get; set; }
}
[Table("Series")]
public class Series
{
[Column("Id"), Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Column("Name")]
public string Name { get; set; }
public virtual ICollection<DiscountSeries> Discounts { get; set; }
}
If I comment out both ICollection lines in Activity and Series, the error no longer occurs. If I leave one of the two, does not matter which, the error still does not occur. Also, it does not seem to matter if the new or old EF database context is used for the LINQ statements when the ICollection objects are both present, the same error is thrown. So to be clear, when running a LINQ statement on the old database context, we get the above error even though models and collections are in a separate database context.
Here is an example in our old API using the old context:
public class ContractsController : ApiController
{
private readonly DatabaseContext _db = new DatabaseContext();
[ResponseType(typeof(Contract))]
public IHttpActionResult Get(int id)
{
var model = _db.Contracts.Find(id);
if (model == null)
{
return NotFound();
}
return Ok(model);
}
}
Any input is appreciated!

MVC 4 EF database first Model constructor updates

I have created an MVC 4 application with EF db-first using ADO.NET Entity Data Model.
I've previously been adding data validation and updating constructors directly into the generated Model classes, but as I foresee these tables to be updated I don't want to have to add these all back in, plus I shouldn't be editing these auto generated classes anyway.
Using Metadata.cs and PartialClasses.cs from http://www.asp.net/mvc/tutorials/mvc-5/database-first-development/enhancing-data-validation I'm not sure the best way to update the default constructors for these Model classes.
Here's an example model, simplified.
Within .edmx
public partial class Campaign
{
public Campaign()
{
this.Fees = new HashSet<Fee>();
}
public int ID { get; set; }
public string Name { get; set; }
public string CreatedBy { get; set; }
public System.DateTime CreatedOnDate { get; set; }
public virtual ICollection<Fee> Fees { get; set; }
}
within ParticalClasses.cs [errors as the generated Modal class defines the default constructor]
[MetadataType(typeof(CampaignMetadata))]
public partial class Campaign
{
public Campaign()
{
this.Fees = new HashSet<Fee>();
// Non-Generated
this.CreatedOnDate = DateTime.Now;
}
}
I have other models I would also like to have other constructors with different parameters, so to simplify my question, where do I add constructors for DB first MVC as to no update the generated Model classes?
Not 100% sure about what you are trying to do, but I'll try to answer your question.
First of all, it seems that you are missing the point of the MVC: your link refers to view model validators, but you are talking about data models. Two VERY different things. There's nothing to validate in a data model - those change and are govern by what's going on in the database.
This is what I would do:
1) Create a data layer: this would hold all your entity classes.
2) Create a service layer: this will instantiate and populate the entity classes using either raw sql, or a pattern (repository pattern, for exam).
3) Create your website: this will hold your controllers, view models (they are the ones you want to validate) and views.
For your Campaign class:
public interface IEntity
{
object EntityID { get; set; }
}
public abstract class BaseEntity: IEntity
{
public abstract object EntityID { get; set; }
}
public class Campaign : BaseEntity
{
#region Properties
public int ID { get; set; }
public string Name { get; set; }
public string CreatedBy { get; set; }
public DateTime CreatedOnDate { get; set; }
public virtual List<Fee> Fees { get; set; }
#endregion
#region BaseEntity Implementation
public override object EntityID
{
get { return this.ID; }
}
#endregion
#region Constructors
public Campaign()
{
this.CreatedOnDate = DateTime.Now;
this.Fees = new List<Fee>();
}
#endregion
}
//View model
//THIS is the class you want to validate
public class CampaignViewModel
{
#region Properties
public int ID { get; set; }
[StringLength(50)]
public string Name { get; set; }
[Required]
public string CreatedBy { get; set; }
public DateTime CreatedOnDate { get; set; }
public Fee AssociatedFee { get; set; }
#endregion
#region Constructors
public CampaignViewModel()
{ }
public CampaignViewModel(Campaign data)
{
this.ID = data.ID
this.Name = data.Name;
this.CreatedBy = data.CreatedBy;
this.CreatedOn = data.CreatedOn;
this.AssociatedFee = data.Fees.Where(x=>x.Active && x.ID == this.ID);
//Just an example
}
#endregion
}
Also, you could use Fluent Validation for a more in-depth separation of concerns. (http://fluentvalidation.codeplex.com/)

Entity Framework 6 - Multiple lookup inserts vs An object with the same key already exists in the ObjectStateManager

I am apparently having a real devil of a time understanding Entity Framework 6 which I am using with ASP.NET MVC 5.
The core of the matter is that I have a really quite simple data model that is typical of any real world situation where I have various business objects that have other business objects as properties (and of course they child objects may in turn have other child business objects) and also various types of lookup/type data (Country, State/Province, LanguageType, StatusType etc.) and I cannot figure out how to save/update it properly.
I keep going back and forth between two error states:
1) I either run into the situation where saving a parent business object results in unwanted duplicate values being inserted into my lookup/type tables (for example saving a business object that has been assigned an existing LanguageType of 'English' will result in another LanguageType for 'English' being inserted into the LanguageType table), or
2) I use some of the suggestions I've seen here and elsewhere on the net (e.g. Saving Entity causes duplicate insert into lookup data, Prevent Entity Framework to Insert Values for Navigational Properties ) to solve issue 1 and then find myself fighting against this same issue: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key .
I will now provide a few code snippets to help build the picture of what I am trying to do and what I am using to do it. First, an example of the entities involved:
public class Customer : BaseEntity
{
public string Name { get; set; }
[LocalizedDisplayName("Contacts")]
public virtual List Contacts { get; set; }
}
public class Contact : BaseEntity
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public int? LanguageTypeID { get; set; }
[Required]
[ForeignKey("LanguageTypeID")]
public virtual LanguageType Language { get; set; }
}
public class LanguageType : Lookup
{
[LocalizedDisplayName("CultureName")]
public string CultureName { get; set; }
}
public class Lookup : BaseEntity
{
public string DisplayName { get; set; }
public int DisplayOrder { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class BaseEntity
{
public int ID { get; set; }
public DateTime? CreatedOn { get; set; }
public DateTime? UpdatedOn { get; set; }
public DateTime? DeletedOn { get; set; }
public bool Deleted { get; set; }
public bool Active { get; set; }
public ApplicationUser CreatedByUser { get; set; }
public ApplicationUser UpdatedByUser { get; set; }
}
In my controller, I have some code like the following:
foreach(Contact contact in lstContacts)
{
customer.Contacts.Add(contact);
}
if (ModelState.IsValid)
{
repository.Add(customer);
}
Let us suppose that each of the contacts has the same LanguageType of 'English' assigned (and in this example it is the fact that I am trying to save multiple contacts that have the same LanguageType that triggers the ObjectStateManager error). Initially, the repository.Add() code just did a context.SaveChanges() which did not work as expected, so now it looks something like this (Entity variable is a Customer):
try
{
if(Entity.Contacts != null)
{
foreach(Contact contact in Entity.Contacts)
{
var entry = this.context.Entry(contact.Language);
var key = contact.Language.ID;
if (entry.State == EntityState.Detached)
{
var currentEntry = this.context.LanguageTypes.Local.SingleOrDefault(l => l.ID == key);
if (currentEntry != null)
{
var attachedEntry = this.context.Entry(currentEntry);
//attachedEntry.CurrentValues.SetValues(entityToUpdate);
attachedEntry.State = EntityState.Unchanged;
}
else
{
this.context.LanguageTypes.Attach(contact.Language);
entry.State = EntityState.Unchanged;
}
}
}
}
context.Customers.Add(Entity);
context.SaveChanges();
}
catch(Exception ex)
{
throw;
}
Is it fundamentally wrong to expect this to have worked? How am I supposed to save and example like this? I have similar problems saving similar object graphs. When I look at tutorials and examples for EF, they are all simple and they all just call SaveChanges() after doing something very similar to what I am doing here.
I've just recently been using the ORM capabilities of ColdFusion (which is hibernate under the covers) and there are would simply load the LanguageType entity, assign it to the Contact entity, save the Contact entity, assign it to the Customer and then save the Customer.
In my mind, this is the most basic of situations and I cannot believe that it has caused me so much pain - I hate to say it, but using plain old ADO.NET (or heaven forbid, ColdFusion which I really don't enjoy) would have been MUCH simpler. So I am missing SOMETHING. I apparently have a key flaw in my understanding/approach to EF and If somebody could help me to make this work as expected and help me to figure out just where my misunderstanding lies, I would greatly appreciate it. I have spend too many hours and hours on this and it is a waste of time - I have/will have countless examples just like this one in the code I am building so I need to adjust my thinking with respect to EF right now so I can be productive and do approach things in the expected way.
Your help will mean so much and I thank you for it!
Let's consider the following object graph in which a teacher instance is the root object,
Teacher --[has many]--> courses
Teacher --[Has One]--> Department
In entity framework's DbContext, each instance of an object has a State indicating whether the object is Added, Modified, Removed or Unchanged. What happens apparently is the following :
Creating the root object for the first time
In this case, in addition to the newly created root object Teacher, ALL the child objects in the graph will have the State Added as well even if they're already created. The solution for this problem is to include the foreign key property for each child element and use it instead, i.e. Teacher.DepartmentId = 3 for example.
Updating the root object and one of its child elements' properties
Suppose you fetch a teacher object from the db, and you change the Teacher.Name property as well as the Teacher.Department.Name property; in this case, only the teacher root object will have the State marked as Modified, the department's State on the other hand remains Unchanged and the modification won't be persisted into DB; Silently without any warning.
EDIT 1
I used your classes as follows and I don't have a problem with persisting the objects :
public class Customer : BaseEntity
{
public string Name { get; set; }
public virtual List<Contact> Contacts { get; set; }
}
public class Contact : BaseEntity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int? LanguageTypeID { get; set; }
public Customer Customer { get; set; }
[ForeignKey("LanguageTypeID")]
public LanguageType Language { get; set; }
}
public class LanguageType : Lookup
{
public string CultureName { get; set; }
}
public class Lookup : BaseEntity
{
public string DisplayName { get; set; }
public int DisplayOrder { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class BaseEntity
{
public int ID { get; set; }
public DateTime? CreatedOn { get; set; }
public DateTime? UpdatedOn { get; set; }
public DateTime? DeletedOn { get; set; }
public bool Deleted { get; set; }
public bool Active { get; set; }
public ApplicationUser CreatedByUser { get; set; }
public ApplicationUser UpdatedByUser { get; set; }
}
public class ApplicationUser
{
public int ID { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
}
And used the following Context :
public class Context : DbContext
{
public Context() : base("name=CS") { }
public DbSet<Customer> Customers { get; set; }
public DbSet<Contact> Contacts { get; set; }
public DbSet<LanguageType> LanguageTypes { get; set; }
public DbSet<ApplicationUser> ApplicationUsers { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//I'm generating the database using those entities you defined;
//Here we're demanding not add 's' to the end of table names
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
Then I created a unit tests class with the following :
[TestMethod]
public void TestMethod1()
{
//our context
var ctx = new Infrastructure.EF.Context();
//our language types
var languageType1 = new LanguageType { ID = 1, Name = "French" };
var languageType2 = new LanguageType { ID = 2, Name = "English" };
ctx.LanguageTypes.AddRange(new LanguageType[] { languageType1, languageType2 });
//persist our language types into db before we continue.
ctx.SaveChanges();
//now we're about to start a new unit of work
var customer = new Customer
{
ID = 1,
Name = "C1",
Contacts = new List<Contact>() //To avoid null exception
};
//notice that we're assigning the id of the language type and not
//an object.
var Contacts = new List<Contact>(new Contact[] {
new Contact{ID=1, Customer = customer, LanguageTypeID=1},
new Contact{ID=2, Customer = customer, LanguageTypeID=2}
});
customer.Contacts.AddRange(Contacts);
//adding the customer here will mark the whole object graph as 'Added'
ctx.Customers.Add(customer);
//The customer & contacts are persisted, and in the DB, the language
//types are not redundant.
ctx.SaveChanges();
}
It all worked smoothly without any problems.
As far as i know there is no build in support for reattaching modified graphs (like the SaveOrUpdate method of nHibernate). Perhaps this or this can help you.

EF5 Model-First, DBContext code generation and derived class

I'm creating a EF5 entity model with the designer (VS2012), and used the EF5 DbContext generator as code generation item.
My model contains an entity deriving from another (not abstract).
So let's say the base entity is called BaseEntity, and the derived entity is DerivedEntity.
Now I see in the generated context class, that there is no
Public DbSet<DerivedEntity> DerivedEntities { get; set; }
defined.
Only
Public DbSet<BaseEntity> BaseEntities { get; set; }
is defined.
Is this normal ? And if yes, how do I query the derived entities in linq ?
I'm used to query like this:
using(var ctx = new EntityContainer)
{
var q = from e in ctx.DerivedEntities <-- but this is now not possible since it doesn't exist
select e;
return q.ToList();
}
Thanks for replying.
EDIT:
As requested, generated classes posted:
public partial class Scheduling
{
public int Id { get; set; }
public string Subject { get; set; }
public System.DateTime BeginDate { get; set; }
public System.DateTime EndDate { get; set; }
}
public partial class TeamScheduling : Scheduling
{
public int TeamId { get; set; }
public Nullable<int> AssignmentId { get; set; }
public virtual Team Team { get; set; }
public virtual Assignment Assignment { get; set; }
}
public partial class EntityContainer : DbContext
{
public EntityContainer()
: base("name=EntityContainer")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public DbSet<Team> Teams { get; set; }
public DbSet<Location> Locations { get; set; }
public DbSet<Country> Countries { get; set; }
public DbSet<Assignment> Assignments { get; set; }
public DbSet<ProductType> ProductTypes { get; set; }
public DbSet<AssignmentPreference> AssignmentPreferences { get; set; }
public DbSet<Scheduling> Schedulings { get; set; }
}
As you see, the EntityContainer class does not contain
public DbSet<TeamScheduling> TeamSchedulings { get; set; }
This is expected when you use inheritance the way you have. context.Schedulings contains both Scheduling objects and TeamScheduling objects. You can get the TeamScheduling objects only by asking for context.Schedulings.OfType<TeamScheduling>(). Note that you cannot meaningfully use context.Schedulings.OfType<Scheduling>() to get the others: that will also include the TeamScheduling objects.
You could alternatively try context.Set<TeamScheduling>(), but I'm not entirely sure that will work.
If your intention is to have two tables come up, say a parent Scheduling entity as well as a child TeamScheduling entity that has a foreign key back to the Scheduling entity, consider using a Table-per-Type (TPT) mapping as discussed here.
In essence, you should modify your "OnModelCreating" method to have the following code:
modelBuilder.Entity<TeamScheduling>().ToTable("TeamScheduling");
This explicitly tells EF that you want to have the TeamScheduling subclass to be represented as its own table. Querying it via LINQ would be simple as you would be able to do something like the following:
var teamScheds = context.Set<TeamScheduling>().Where(s => s.Id == 1).FirstOrDefault();

Categories