简体   繁体   中英

Open Entity in another DBContext in Entity Framework

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?

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 the ObjectMaterialized event (see example in this answer ):

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

    if ( entity != null )
    {
        entity.OnMaterialized();
    }
}

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM