Entity Framework: Developing a Model that does not do Updates and Deletes - entity-framework

I am trying to figure out a way to develop a database model using Entity Framework that does not do updates or deletes. The business requirements want the complete history of all changes that are made to each record in the system, for analysis reasons. So instead I want to always modify by inserting a new record to the database.
Is there a clean way to get Entity Framework to do that? Or am I going to be jumping through a lot hoops to get this sort of behavior. The basic model is pretty simple, some stuff, like constructors, removed since they don't add much to the discussion:
public class Container
{
public Guid Id { get; private set; }
public ICollection<Container> RelatedContainers { get; private set; }
public ICollection<Item> Items { get; private set; }
}
public class Item
{
public Guid Id { get; private set; }
public string Name { get; private set; }
public string Value { get; private set; }
}

Basically you need to override SaveChanges() method in DbContext. In your method get all the objects that have the EntityState Deleted or Modified and set the status UnChanged.
public class YourDbContext:DbContext{
public override int SaveChanges(){
foreach ( var ent in this.ChangeTracker
.Entries()
.Where(p =>p.State == System.Data.EntityState.Deleted
p.State == System.Data.EntityState.Modified))
{
ent.State =System.Data.EntityState.Unchanged;
}
}
}

Related

EF : Returning linked tables

Is this the correct way I can get linked tables using where or is there another way to return the data.
GetMethod
public IEnumerable<Model.MeetingPollingQuestion> GetMeetingPollingQuestion(int MeetingPollingId)
{
using (var db = new NccnEcommerceEntities())
{
using (DbContextTransaction dbTran = db.Database.BeginTransaction())
{
var ListOfMeetingPollingQuestions = (from mpq in db.MeetingPollingQuestions
where (mpq.MeetingPollingId == MeetingPollingId)
select new Model.MeetingPollingQuestion
{
MeetingPollingId = mpq.MeetingPollingId.Value,
MeetingPollingQuestionType = mpq.MeetingPollingQuestionType,
MeetingPollingParts = (from mp in db.MeetingPollingParts
where mp.MeetingPollingPartsId == mpq.MeetingPollingId
select new Model.MeetingPollingParts
{
Type= mp.Type
}).ToList(),
}).ToList();
return ListOfMeetingPollingQuestions;
}
}
}
EF Model
namespace Repository.EFModel
{
using System;
using System.Collections.Generic;
public partial class MeetingPolling
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public MeetingPolling()
{
this.MeetingPollingQuestions = new HashSet<MeetingPollingQuestion>();
}
public int MeetingPollingId { get; set; }
public Nullable<int> MeetingId { get; set; }
public Nullable<System.DateTime> StartDate { get; set; }
public Nullable<System.DateTime> EndDate { get; set; }
public string PollingTitle { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<MeetingPollingQuestion> MeetingPollingQuestions { get; set; }
}
}
namespace Repository.EFModel
{
using System;
using System.Collections.Generic;
public partial class MeetingPollingQuestion
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public MeetingPollingQuestion()
{
this.MeetingPollingParts = new HashSet<MeetingPollingPart>();
}
public int MeetingPollingQuestionId { get; set; }
public string MeetingPollingQuestionType { get; set; }
public Nullable<int> MeetingPollingId { get; set; }
public Nullable<int> SequenceOrder { get; set; }
public virtual MeetingPolling MeetingPolling { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<MeetingPollingPart> MeetingPollingParts { get; set; }
}
}
namespace Repository.EFModel
{
using System;
using System.Collections.Generic;
public partial class MeetingPollingPart
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public MeetingPollingPart()
{
this.MeetingPollingPartsValues = new HashSet<MeetingPollingPartsValue>();
}
public int MeetingPollingPartsId { get; set; }
public string Type { get; set; }
public Nullable<int> MeetingPollingQuestionId { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<MeetingPollingPartsValue> MeetingPollingPartsValues { get; set; }
public virtual MeetingPollingQuestion MeetingPollingQuestion { get; set; }
}
}
Your entities have navigation properties for the related entities, so if you want to return entities and their relatives, just eager load the related entities using Include.
public IEnumerable<Model.MeetingPollingQuestion> GetMeetingPollingQuestion(int MeetingPollingId)
{
using (var db = new NccnEcommerceEntities())
{
var questions = db.MeetingPollingQuestions
.AsNoTracking()
.Include(mpq => mpq.MeetingPollingType)
.Include(mpq => mpq.MeetingPollingParts)
.Where(mpq => mpq.MeetingPollingId == MeetingPollingId)
.ToList();
return questions;
}
}
... and that's it. The important details here is the use of Include to eager load related data, and the use of AsNoTracking to ensure that the loaded entities are not tracked by the DbContext. These will be detached entities, copies of the data but otherwise not tracking changes or an association with a DbContext. These are suitable for read-only access to the data they contain.
Whenever returning "entities" outside of the scope of a DbContext you should ensure that they are non-tracked or detached. This is to avoid errors that can come up with potential lazy load scenarios complaining about that an associated DbContext has been disposed, or errors about references being tracked by another DbContext if you try associating these entities to a new DbContext. Your code performing a Select with a new class instance does the same thing, just more code.
Personally I do not recommend working with detached entities such as ever returning entities outside of the scope of the DbContext that they were read from. Especially if you decide you only need a subset of data that the entity ultimately can provide. Entities reflect the data state, and should always be considered as Complete, or Complete-able. (i.e. Lazy loading enabled) If the code base has some code that works with tracked entities within the scope of a DbContext, vs. detached entities, vs. copies of entity classes that might be partially filled or deserialized, it makes for buggy, unreliable, and un-reusable code. Take a utility method that accepts a MeetingPollingQuestion as a parameter. As far as that method is concerned it should always get a complete MeetingPollingQuestion. The behaviour of this method could change depending on whether it was given a tracked vs. detached, vs. partially filled in copy of a MeetingPollingQuestion class. Methods like this would need to inspect the entity being passed in, and how reliable can that logic be for determining why a related entity/collection might be missing or #null?
If you need to pass entity data outside the scope of the DbContext where you cannot count on that data being complete or related data being lazy loaded as a last resort, then I recommend using POCO DTOs or ViewModels for those detached or partial representations of the data state. With a separate POCO (Populated by Select or Automapper) there is no confusion between that representation and a tracked Entity.

Why MVC does autoupdate of EF Model Classes?

I generated Entity Model from my database in my MVC5 App.
When I try to add [DispalyName] to some properties it works fine, but after some time app refreshes this class by itself and removes all my custom code
public partial class Patient
{
public Patient()
{
this.PatientDetails = new HashSet<PatientDetail>();
}
public int PatientID { get; set; }
[DisplayName("Last Name")]
public string LastName { get; set; }
public string FirstName { get; set; }
public virtual ICollection<PatientDetail> PatientDetails { get; set; }
}
Why MVC does it and how to disable that?
I believe since you're using Database first, the entities will get completely re-created every time you refresh, thus you lose your custom attributes.
Also, to go off of Joe's comment, you should make a view model and put your [Display] attributes there and not directly on the entity.

Attach a related property in the CollectionChanged event of entity object

Any help gratefully received on a little issue I have.
I have an entity framework class
public partial class BookingProduct
{
public BookingProduct()
{
this.BookingDesigns = new ObservableCollection<BookingDesign>();
this.BookingDesigns.CollectionChanged += ContentCollectionChanged;
}
public int BookingProductId { get; set; }
public int ProductId { get; set; }
public decimal Price { get; set; }
public virtual ObservableCollection<BookingDesign> BookingDesigns { get; set; }
public virtual Product Product { get; set; }
public void ContentCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
//Retrieve Product here
}
}
In my application I am retrieving a BookingProduct with BookingDesigns included from the database but not the Product. I'm wondering if within the entity class there is a way to retrieve the Product from with the ContentCollectionChanged event?
Thanks in advance for any pointers
Sure, you could simply access the property to have it lazy load from within the ContentCollectionChanged method, but I question why you would want to do that. At best the solution would be confusing - why manage the state of the Product property in a method meant to handle changes to an unrelated collection?

Entity Framework, Code First, Update "one to many" relationship with independent associations

It took me way too long to find a solution to the scenario described below. What should seemingly be a simple affair proved to be rather difficult. The question is:
Using Entity Framework 4.1 (Code First approach) and "Independent associations" how do I assign a different end to an existing "many to one" relationship in a "detached" scenario ( Asp.Net in my case).
The model:
I realize that using ForeignKey relationships instead of Independent Associations would have been an option, but it was my preference to not have a ForeignKey implementation in my Pocos.
A Customer has one or more Targets:
public class Customer:Person
{
public string Number { get; set; }
public string NameContactPerson { get; set; }
private ICollection<Target> _targets;
// Independent Association
public virtual ICollection<Target> Targets
{
get { return _targets ?? (_targets = new Collection<Target>()); }
set { _targets = value; }
}
}
A Target has one Customer:
public class Target:EntityBase
{
public string Name { get; set; }
public string Description { get; set; }
public string Note { get; set; }
public virtual Address Address { get; set; }
public virtual Customer Customer { get; set; }
}
Customer derives from a Person class:
public class Person:EntityBase
{
public string Salutation { get; set; }
public string Title { get; set; }
public string FirstName { get; set; }
public string LastName { get; set ; }
public string Telephone1 { get; set; }
public string Telephone2 { get; set; }
public string Email { get; set; }
public virtual Address Address { get; set; }
}
EntityBase class provides some common properties:
public abstract class EntityBase : INotifyPropertyChanged
{
public EntityBase()
{
CreateDate = DateTime.Now;
ChangeDate = CreateDate;
CreateUser = HttpContext.Current.User.Identity.Name;
ChangeUser = CreateUser;
PropertyChanged += EntityBase_PropertyChanged;
}
public void EntityBase_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (Id != new Guid())
{
ChangeDate = DateTime.Now;
ChangeUser = HttpContext.Current.User.Identity.Name;
}
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, e);
}
public event PropertyChangedEventHandler PropertyChanged;
public Guid Id { get; set; }
public DateTime CreateDate { get; set; }
public DateTime? ChangeDate { get; set; }
public string CreateUser { get; set; }
public string ChangeUser { get; set; }
}
The Context:
public class TgrDbContext : DbContext
{
public DbSet<Person> Persons { get; set; }
public DbSet<Address> Addresses { get; set; }
public DbSet<Customer> Customers { get; set; }
public DbSet<Target> Targets { get; set; }
public DbSet<ReportRequest> ReportRequests { get; set; }
// If OnModelCreating becomes to big, use "Model Configuration Classes"
//(derived from EntityTypeConfiguration) instead
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>().HasOptional(e => e.Address);
modelBuilder.Entity<Customer>().HasMany(c => c.Targets).WithRequired(t => t.Customer);
}
public static ObjectContext TgrObjectContext(TgrDbContext tgrDbContext)
{
return ((IObjectContextAdapter)tgrDbContext).ObjectContext;
}
}
I waited for #Martin answer because there are more solutions for this problem. Here is another one (at least it works with ObjectContext API so it should work with DbContext API as well):
// Existing customer
var customer = new Customer() { Id = customerId };
// Another existing customer
var customer2 = new Customer() { Id = customerId2 };
var target = new Target { ID = oldTargetId };
// Make connection between target and old customer
target.Customer = customer;
// Attach target with old customer
context.Targets.Attach(target);
// Attach second customer
context.Customers.Attach(customer2);
// Set customer to a new value on attached object (it will delete old relation and add new one)
target.Customer = customer2;
// Change target's state to Modified
context.Entry(target).State = EntityState.Modified;
context.SaveChanges();
The problem here is internal state model and state validations inside EF. Entity in unchanged or modified state with mandatory relation (on many side) cannot have independent association in added state when there is no other in deleted state. Modified state for association is not allowed at all.
There is a lot of information to be found on this topic; on stackoverflow I found Ladislav Mrnka's insights particularly helpful. More on the subject can also be found here: NTier Improvements for Entity Framework and here What's new in Entity Framework 4?
In my project (Asp.Net Webforms) the user has the option to replace the Customer assigned to a Target object with a different (existing) Customer object. This transaction is performed by a FormView control bound to an ObjectDataSource. The ObjectDataSource communicates with the BusinessLogic layer of the project which in turns passes the transaction to a repository class for the Target object in the DataAccess layer. The Update method for the Target object in the repository class looks like this:
public void UpdateTarget(Target target, Target origTarget)
{
try
{
// It is not possible to handle updating one to many relationships (i.e. assign a
// different Customer to a Target) with "Independent Associations" in Code First.
// (It is possible when using "ForeignKey Associations" instead of "Independent
// Associations" but this brings about a different set of problems.)
// In order to update one to many relationships formed by "Independent Associations"
// it is necessary to resort to using the ObjectContext class (derived from an
// instance of DbContext) and 'manually' update the relationship between Target and Customer.
// Get ObjectContext from DbContext - ((IObjectContextAdapter)tgrDbContext).ObjectContext;
ObjectContext tgrObjectContext = TgrDbContext.TgrObjectContext(_tgrDbContext);
// Attach the original origTarget and update it with the current values contained in target
// This does NOT update changes that occurred in an "Independent Association"; if target
// has a different Customer assigned than origTarget this will go unrecognized
tgrObjectContext.AttachTo("Targets", origTarget);
tgrObjectContext.ApplyCurrentValues("Targets", target);
// This will take care of changes in an "Independent Association". A Customer has many
// Targets but any Target has exactly one Customer. Therefore the order of the two
// ChangeRelationshipState statements is important: Delete has to occur first, otherwise
// Target would have temporarily two Customers assigned.
tgrObjectContext.ObjectStateManager.ChangeRelationshipState(
origTarget,
origTarget.Customer,
o => o.Customer,
EntityState.Deleted);
tgrObjectContext.ObjectStateManager.ChangeRelationshipState(
origTarget,
target.Customer,
o => o.Customer,
EntityState.Added);
// Commit
tgrObjectContext.Refresh(RefreshMode.ClientWins, origTarget);
tgrObjectContext.SaveChanges();
}
catch (Exception)
{
throw;
}
}
This works for the Update method for the Target object. Remarkably, the procedure for inserting a new Target object is way easier. DbContext recognizes the Customer end of the independent association properly and commits the change to the database without further ado. The Insert method in the repository class looks like this:
public void InsertTarget(Target target)
{
try
{
_tgrDbContext.Targets.Add(target);
_tgrDbContext.SaveChanges();
}
catch (Exception)
{
throw;
}
}
Hopefully this will be useful to somebody dealing with a similar task. If you notice a problem with this approach described above, please let me know in your comments. Thanks!

Entity Framework: a proxy collection for displaying a subset of data

Imagine I have an entity called Product and a repository for it:
public class Product
{
public int Id { get; set; }
public bool IsHidden { get; set; }
}
public class ProductRepository
{
public ObservableCollection<Product> AllProducts { get; set; }
public ObservableCollection<Product> HiddenProducts { get; set; }
}
All products contains every single Product in the database, while HiddenProducts must only contain those, whose IsHidden == true. I wrote the type as ObservableCollection<Product>, but it does not have to be that.
The goal is to have HiddenProducts collection be like a proxy to AllProducts with filtering capabilities and for it to refresh every time when IsHidden attribute of a Product is changed.
Is there a normal way to do this? Or maybe my logic is wrong and this could be done is a better way?
Ended up on CollectionView/CollectionViewSource stuff.