ChangeTracker Entity Framework 4.1 - оригинальные значения связанных объектов - PullRequest
9 голосов
/ 16 августа 2011

У меня есть базовый класс, который я унаследовал, у которого есть два отношения ноль ко многим с другими объектами:

public abstract class WebObject
{
    public WebObject()
    {
        RelatedTags = new List<Tag>();
        RelatedWebObjects = new List<WebObject>();
    }

    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid Id { get; set; }

    public string MetaKeywords { get; set; }
    public string MetaDescription { get; set; }

    [InverseProperty("WebObjects")]
    public virtual WebSite WebSite { get; set; }

    [Required(ErrorMessage = "Every WebObject must be associated with a WebSite.")]
    public Guid WebSiteId { get; set; }

    public virtual ICollection<Tag> RelatedTags { get; set; }
    public IList<Guid> RelatedTagIds { get; set; }
    public virtual ICollection<WebObject> RelatedWebObjects { get; set; }
    public IList<Guid> RelatedWebObjectIds { get; set; }
}

У меня возникают трудности при получении исходных значений для этих отношений (RelatedWebObjects & RelatedTags) при поискеу сущностей, использующих ChangeTracker во время SaveChanges.Я могу видеть все скалярные значения до и после, и я вижу новые отношения, но я не могу видеть старые.Я пытался использовать методы Member и Collection, но они показывают только текущие значения;не старый.Также я не люблю их использовать, потому что это требует, чтобы я знал имя свойства навигации, что недостаточно универсально.

Я могу найти связанные объекты, отношения которых изменяются, но, конечно, значения в этих связанных объектах не меняются, так что это тоже не поможет.

Есть ли некоторыечистый способ отслеживать предыдущие отношения сущности во время SaveChanges с ChangeTracker?

Ниже приведен раздел кода, над которым я работаю:

    public override int SaveChanges()
    {
        List<AuditObject> auditTrailList = new List<AuditObject>();

        foreach (DbEntityEntry entity in ChangeTracker.Entries().Where(obj => { return obj.State == EntityState.Added || obj.State == EntityState.Modified || obj.State == EntityState.Deleted; }))
        {
            if (!(entity.Entity is AuditObject))
            {
                AuditObject auditObject = new AuditObject();

                auditObject.Id = Guid.NewGuid();

                auditObject.RevisionStamp = DateTime.Now;

                auditObject.UserName = HttpContext.Current.User.Identity.Name;

                auditObject.EntityType = Utilities.GetCleanClassNameIfProxyClass(entity.Entity.GetType().Name);

                if (entity.State == EntityState.Added)
                    auditObject.Action = EntityState.Added.ToString();
                else if (entity.State == EntityState.Modified)
                    auditObject.Action = EntityState.Modified.ToString();
                else if (entity.State == EntityState.Deleted)
                    auditObject.Action = EntityState.Deleted.ToString();

                DbMemberEntry t1 = entity.Member("RelatedWebObjects");
                // cannot find original relationship collection...

                DbCollectionEntry t2 = entity.Collection("RelatedWebObjects");
                // cannot find original relationship collection...

                if (entity.State == EntityState.Added || entity.State == EntityState.Modified)
                {
                    XDocument currentValues = new XDocument(new XElement(auditObject.EntityType));

                    foreach (string propertyName in entity.CurrentValues.PropertyNames)
                    {
                        currentValues.Root.Add(new XElement(propertyName, entity.CurrentValues[propertyName]));
                    }

                    auditObject.NewData = Regex.Replace(currentValues.ToString(), @"\r\n+", " ");
                }

                if (entity.State == EntityState.Modified || entity.State == EntityState.Deleted)
                {
                    XDocument originalValues = new XDocument(new XElement(auditObject.EntityType));

                    foreach (string propertyName in entity.OriginalValues.PropertyNames)
                    {
                        originalValues.Root.Add(new XElement(propertyName, entity.OriginalValues[propertyName]));
                    }

                    auditObject.OldData = Regex.Replace(originalValues.ToString(), @"\r\n+", " ");
                }

                auditTrailList.Add(auditObject);
            }
        }

        foreach (var audit in auditTrailList)
            this.AuditObjects.Add(audit);

        return base.SaveChanges();
    }

Ответы [ 2 ]

13 голосов
/ 20 августа 2011

Ну, это немного сложно. Прежде всего вы должны различать два типа отношений , предлагаемых EF:

  • Независимая ассоциация (все отношения "многие ко многим" и "один ко многим")
  • Ассоциация внешнего ключа (все отношения один-к-одному и некоторые-ко-многим)

Теперь, если вы хотите узнать предыдущее значение связи внешнего ключа, вам просто нужно отследить изменения в зависимом объекте, где вы выставили свойство внешнего ключа - это точно так же, как отслеживание любого другого изменения свойства.

Если вы хотите отслеживать изменения в независимой ассоциации, ситуация станет более сложной, поскольку DbContext API не предоставляет операций для их отслеживания . Вы должны вернуться к ObjectContext API и его ObjectStateManager.

ObjectContext objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;
foreach (ObjectStateEntry entry = objectContext.ObjectStateManager
                                               .GetObjectStateEntries(~EntityState.Detached)
                                               .Where(e => e.IsRelationship))
{
    // Track changes here
}

Теперь у вас есть доступ к ObjectStateEntry экземплярам для отношений. Эти экземпляры никогда не должны иметь состояния Modified. Они будут либо Added, Deleted, либо Unchanged, потому что «изменение» обрабатывается как удаление старого отношения и добавление нового. ObjectStateEntry также содержит CurrentValues и OriginalValues коллекций. Эти коллекции также должны содержать два элемента, каждый из которых представляет EntityKey сущности на одной стороне отношения.

4 голосов
/ 20 августа 2011

Поскольку EF change отслеживает каждый объект на вашем графике, вы всегда можете передать любой экземпляр на графике в трекер изменений, и он даст вам значения отслеживания изменений. Например, следующий код получит исходные / текущие значения свойства навигации AuditObject:

DbMemberEntry t1 = entity.Member("RelatedWebObjects");
// cannot find original relationship collection....

AuditObject currentAuditObject = (AuditObject) entity;
var currValues = this.Entry(currentAuditObject.RelatedWebObjects).CurrentValues;
var orgValues = this.Entry(currentAuditObject.RelatedWebObjects).OriginalValues;

Или вы можете применить тот же трюк, когда имеете дело со свойством навигации типа коллекции:

DbCollectionEntry t2 = entity.Collection("RelatedWebObjects");
// cannot find original relationship collection....

foreach (WebObject item in currentAuditObject.RelatedWebObjects)
{
    var currValues = this.Entry(item).CurrentValues;
}
...