ChangeTracker Entity Framework 4.1 — исходные значения связанных объектов

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

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, но они показывают мне только текущие значения; не старый. Также мне не нравится их использовать, потому что мне нужно знать имя свойства навигации, которое недостаточно универсально.

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

Есть ли какой-то чистый способ отслеживать предыдущие отношения объекта во время сохранения изменений с помощью 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();
    }

person DMC    schedule 16.08.2011    source источник
comment
Связанные объекты также изменяются, отслеживаемые ObjectStateManager, и вы должны иметь возможность получать записи состояния их объектов точно так же, как вы получаете их для своего основного объекта. Если вы опубликуете код, с которым вы боретесь, я мог бы помочь.   -  person Morteza Manavi    schedule 17.08.2011
comment
Еще раз спасибо, Мортеза... Я выложил часть кода, над которой сейчас работаю - прошу извинить за небрежность; он вообще не подвергался рефакторингу - просто пытаюсь заставить его работать. Я могу без проблем получить скалярные свойства объектов, используя строки IEnumerable: entity.OriginalValues.PropertyNames & entity.CurrentValues.PropertyNames. У меня проблемы с entity.Collection() и entity.Member(). Что следует использовать для того, что я пытаюсь выполнить? Есть ли способ сделать это общим, чтобы мне не приходилось жестко кодировать имена коллекций? Может отражение?   -  person DMC    schedule 17.08.2011
comment
я не в состоянии попробовать это до завтра ... кто-нибудь из вас знает, как я могу разделить баллы между двумя ответами? потому что я думаю, что вы оба дали очень ценную информацию.   -  person DMC    schedule 21.08.2011


Ответы (2)


Поскольку изменение EF отслеживает каждый объект на вашем графике, вы всегда можете передать любой экземпляр на графике средству отслеживания изменений, и оно предоставит вам значения отслеживания изменений. Например, следующий код получит исходное/текущее значения свойства навигации объекта 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;
}
person Morteza Manavi    schedule 20.08.2011
comment
еще раз спасибо, Мортеза! Я ценю, что ты вернулся ко мне так быстро. также еще раз спасибо за блог! - person DMC; 22.08.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 сущности на одной стороне отношения.

person Ladislav Mrnka    schedule 20.08.2011
comment
Спасибо большое за помощь! я вижу, что это работает. также я узнал о побитовом операторе ~ - никогда не использовал его раньше. Вы не знаете, можно ли присвоить очки и вам, и Мортезе? его ответ пришел первым и тоже работает, и я связался с ним специально через его блог, но я хочу, чтобы вы оба получили признание ... - person DMC; 22.08.2011
comment
@DMC: Вы можете пометить только один ответ как принятый, но в зависимости от вашей репутации вы также можете проголосовать за любое количество ответов. - person Ladislav Mrnka; 22.08.2011
comment
извините, я отдам его Мортезе, потому что он получил его первым, и я изо всех сил старался связаться с ним напрямую по этому поводу. но я дал вам голос за это. - person DMC; 22.08.2011