0

I have an entity loaded from one DBContext with some changes made to it. If I then create another DBContext how would I load the same entity into it from the database? This needs to be generic across different entities, so I can't just lookup where the ID's are the same - some entities could have different key properties.

The reason behind the question is that I'm wanting to use the entity loaded into the second context to validate some changed properties in the first one, comparing previous values to new ones. So the second context is purely read only during the validation of the first entity.


Edit

I tried to keep this simple I think a bit more detail is needed.

Say I have this entity:

public partial class SomeEntity : IValidatableObject
{
    public int Id { get; set; }

    public int StatusId { get; set; }

    //... Other properties

    public virtual ICollection<SomeOtherEntity> relatedEntity { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext){
        // validation code
    }
}

What I'm trying to do is when the entity is validated the StatusId is compared against the StatusId currently in the database and a validation error could occur - for example say StatusId can't be changed from 1 to 5. As

As Pawel mentioned this can be done by using the OriginalValues however I also need to be able to validate the StatusId based on the values in the relatedEntity. For example say the StatusId can't be changed from 1 to 2 if certain values exist in the relatedEntity.

So in order to do this validation I need a duplicate of the SomeEntity object in its unmodifed form from the database so it can be compared against the object I'm trying to modify.

It's a bit messy but the idea I've come up with is:

  • Make SomeEntity a member of a new, empty, interface IMyValidationInterface.

    public interface IMyValidationInterface
    {
    }
    
    public partial class SomeEntity : IValidatableObject, IMyValidationInterface
    {
        public int Id { get; set; }
        // ....
    }
    
  • Override the ValidateEntity method on MyDBContext so that an object with the original values is passed into the validation method on the entity.

    public class MyDBContext : DbContext
    {
        protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
        {
            if (entityEntry.Entity is IMyValidationInterface)
            {
                var _validationContext = new MyDbContext();
    
                /// TODO: some code here to load a duplicated version of 
                ///       the entity from the database
    
                var originalEntity; // unchanged entity is here
    
                // unmodified entity is passed as an item to the entities
                // Validate method 
                items.Add("OriginalEntity", originalEntity);
             }
    
             return base.ValidateEntity(entityEntry, items);
         }
    }
    
    public partial class SomeEntity : IValidatableObject, IMyValidationInterface
    {
        // .....
    
        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext){
            // validation code
            if (validationContext.Items.ContainsKey("OriginalEntity")){
                  var originalEntity = (SomeEntity)validationContext.Items["OriginalEntity"];
    
                  // do validation here and yield return any validation errors
            }
        }
    }
    

The bit I'm stuck on is the TODO part in the above snippet.

I'm I going at this the wrong way?

Rich
  • 561
  • 1
  • 4
  • 11
  • Are you saving the changes on your modified entity loaded from the first DBContext? – Adolfo Perez Sep 09 '14 at 20:25
  • Maybe you can use OriginalValues and don't have to have multiple contexts? – Pawel Sep 09 '14 at 20:26
  • I think you just need to know what changes are made to the entity for validation. Please check this link if it helps, [LINK]: http://stackoverflow.com/questions/3265257/getting-all-changes-made-to-an-object-in-the-entity-framework – Ganesh Nemade Sep 09 '14 at 22:26
  • Have you looked into querying the EF model directly to get the actual key information from the entities? – ESG Sep 10 '14 at 00:20

2 Answers2

0

A quick-and-dirty way would be to store the original status before modifying it into its own property or field that isn't mapped to the database, ideally when it is loaded from the database.

interface IMaterializable
{
    void OnMaterialized();
}

class SomeEntity : IMaterializable, IValidatableObject
{
    public int Id { get; set; }

    public int StatusId { get; set; }

    //... Other properties

    public virtual ICollection<SomeOtherEntity> relatedEntity { get; set; }

    int OriginalStatusId;

    public void OnMaterialized()
    {
        OriginalStatusId = StatusId;
    }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        // compare current status to original + related
    }
}

And then add this handler to theObjectMaterialized event (see example in this answer):

void OnObjectMaterialized( object sender, ObjectMaterializedEventArgs e )
{
    var entity = e.Entity as IMaterializable;

    if ( entity != null )
    {
        entity.OnMaterialized();
    }
}
Community
  • 1
  • 1
Sean
  • 429
  • 2
  • 9
  • This is my plan B. Really I'd like to make sure I have an up-to-date version from the database before saving. Potentially the `OriginalStatusId` could be different in the database if it gets changed by someone else after the model is created but before its saved. Loading it in again during validation will reduce this risk. – Rich Sep 10 '14 at 10:53
0

I ended up doing this to the datacontext:

public class MyDBContext : DbContext
{
    public virtual T GetEntityByPrimaryKey<T>(T entity) where T : class
    {
        var entityType = entity.GetType();
        var objectSet = ((IObjectContextAdapter)this).ObjectContext.CreateObjectSet<T>();
        var keyNames = objectSet.EntitySet.ElementType.KeyMembers.Select(edmMember => edmMember.Name);
        var keyValues = keyNames.Select(name => entityType.GetProperty(name).GetValue(entity, null)).ToArray();

        return this.Set<T>().Find(keyValues);
    }

    protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
    {
        if (entityEntry.Entity is IMyValidationInterface)
        {
            var validationContext = new MyDBContext();

            var modifiedEntity = entityEntry.Entity;

            var originalEntity = validationContext.GetEntityByPrimaryKey(a);

            items.Add("OriginalEntity", originalEntity);
        }

        return base.ValidateEntity(entityEntry, items);
    }
}

GetEntityByPrimaryKey was found here Entity Framework Get Entity By Primary Key

Community
  • 1
  • 1
Rich
  • 561
  • 1
  • 4
  • 11