Подтвердить что ты не робот

Могу ли я использовать Entity Framework версии 6 или 7 для автоматического обновления объекта и его детей?

У меня три таблицы. Word → WordForm → SampleSentence. Каждый Word имеет разные WordForms, а затем каждая форма может иметь один или несколько SampleSentence

CREATE TABLE [dbo].[Word] (
    [WordId]       VARCHAR (20) NOT NULL,
    [CategoryId]   INT          DEFAULT ((1)) NOT NULL,
    [GroupId]      INT          DEFAULT ((1)) NOT NULL,
    PRIMARY KEY CLUSTERED ([WordId] ASC),
    CONSTRAINT [FK_WordWordCategory] FOREIGN KEY ([CategoryId]) REFERENCES [dbo].[WordCategory] ([WordCategoryId]),
    CONSTRAINT [FK_WordWordGroup] FOREIGN KEY ([GroupId]) REFERENCES [dbo].[WordGroup] ([WordGroupId])
);

CREATE TABLE [dbo].[WordForm] (
    [WordFormId]   VARCHAR (20)  NOT NULL,
    [WordId]       VARCHAR (20)  NOT NULL,
    [Primary]      BIT           DEFAULT ((0)) NOT NULL,
    [PosId]        INT           NOT NULL,
    [Definition]   VARCHAR (MAX) NULL,
    PRIMARY KEY CLUSTERED ([WordFormId] ASC),
    CONSTRAINT [FK_WordFormPos] FOREIGN KEY ([PosId]) REFERENCES [dbo].[Pos] ([PosId]),
    CONSTRAINT [FK_WordFormWord] FOREIGN KEY ([WordId]) REFERENCES [dbo].[Word] ([WordId])
);


CREATE TABLE [dbo].[SampleSentence] (
    [SampleSentenceId] INT           IDENTITY (1, 1) NOT NULL,
    [WordFormId]       VARCHAR (20)  NOT NULL,
    [Text]             VARCHAR (MAX) NOT NULL,
    CONSTRAINT [PK_SampleSentence] PRIMARY KEY CLUSTERED ([SampleSentenceId] ASC),
    CONSTRAINT [FK_SampleSentenceWordForm] FOREIGN KEY ([WordFormId]) REFERENCES [dbo].[WordForm] ([WordFormId])
);

Я беру данные из этих таблиц на внешний клиент, а затем меняет данные и добавляет или удаляет WordForms и SampleSentences.

Затем я возвращаю данные на сервер.

Есть ли способ, с помощью которого Entity Framework может проверять изменения в объекте, который я возвращаю на сервер, и вносить изменения в базу данных, или мне нужно сделать некоторую форму сравнения, где я проверяю до и после Word, WordForm и Sample Sentence?

Для справки здесь используются объекты С#:

public class Word
    {
        public string WordId { get; set; } // WordId (Primary key) (length: 20)
        public int CategoryId { get; set; } // CategoryId
        public int GroupId { get; set; } // GroupId

        // Reverse navigation
        public virtual System.Collections.Generic.ICollection<WordForm> WordForms { get; set; } // WordForm.FK_WordFormWord

        // Foreign keys
        public virtual WordCategory WordCategory { get; set; } // FK_WordWordCategory
        public virtual WordGroup WordGroup { get; set; } // FK_WordWordGroup

        public Word()
        {
            CategoryId = 1;
            GroupId = 1;
            WordForms = new System.Collections.Generic.List<WordForm>();
        }
    }

public class WordForm
    {
        public string WordFormId { get; set; } // WordFormId (Primary key) (length: 20)
        public string WordId { get; set; } // WordId (length: 20)
        public bool Primary { get; set; } // Primary
        public int PosId { get; set; } // PosId
        public string Definition { get; set; } // Definition

        // Reverse navigation
        public virtual System.Collections.Generic.ICollection<SampleSentence> SampleSentences { get; set; } // SampleSentence.FK_SampleSentenceWordForm

        // Foreign keys
        public virtual Pos Pos { get; set; } // FK_WordFormPos
        public virtual Word Word { get; set; } // FK_WordFormWord

        public WordForm()
        {
            Primary = false;
            SampleSentences = new System.Collections.Generic.List<SampleSentence>();
        }
    }

public class SampleSentence : AuditableTable
    {
        public int SampleSentenceId { get; set; } // SampleSentenceId (Primary key)
        public string WordFormId { get; set; } // WordFormId (length: 20)
        public string Text { get; set; } // Text

        // Foreign keys
        public virtual WordForm WordForm { get; set; } // FK_SampleSentenceWordForm
    }

Вот что мне удалось найти до сих пор, но это не включает проверку SampleSentence, и я не уверен, как это сделать:

    public async Task<IHttpActionResult> Put([FromBody]Word word)
    {
        var oldObj = db.WordForms
            .Where(w => w.WordId == word.WordId)
            .AsNoTracking()
            .ToList();
        var newObj = word.WordForms.ToList();

        var upd = newObj.Where(n => oldObj.Any(o =>
            (o.WordFormId == n.WordFormId) && (o.PosId != n.PosId || !o.Definition.Equals(n.Definition) )))
            .ToList();
        var add = newObj.Where(n => oldObj.All(o => o.WordFormId != n.WordFormId))
            .ToList();
        var del = oldObj.Where(o => newObj.All(n => n.WordFormId != o.WordFormId))
            .ToList();
        foreach (var wordForm in upd)
        {
            db.WordForms.Attach(wordForm);
            db.Entry(wordForm).State = EntityState.Modified;
        }
        foreach (var wordForm in add)
        {
            db.WordForms.Add(wordForm);
        }
        foreach (var wordForm in del)
        {
            db.WordForms.Attach(wordForm);
            db.WordForms.Remove(wordForm);
        }
        db.Words.Attach(word);
        db.Entry(word).State = EntityState.Modified;
        await db.SaveChangesAsync(User, DateTime.UtcNow);
        return Ok(word);
    }
4b9b3361

Ответ 1

Извините, нет

Ответ на ваш вопрос буквально (как в названии) нет. Невозможно сделать это автоматически с помощью Entity Framework. В так называемых отключенных сценариях правильное сохранение изменений от клиента - это то, что разработчики должны заботиться о себе.

Как уже упоминалось, EF использовал объекты самоконтроля, но вскоре этот подход был устарел, хотя в официальной документации он никогда не был четко объяснен Зачем. Вероятно, потому что "STEs сделали (отслеживание изменений) проще, но ценой сделать почти все остальное действительно тяжело.." Он отлично подойдет API ObjectContext с базовыми классами с базой данных с шаблонами t4, но, как мы все знаем, API-интерфейс DbContext и первый код стали EF рекомендованной (и вскоре единственной поддерживаемой) архитектурой. Конечно, с кодовым первым EF не может обеспечить реализацию STE.

Или...?

Несколько разочаровывает тот факт, что EF никогда не заполнил этот пробел позже, например, предоставив API, аналогичный тому, что GraphDiff предлагает (или, возможно, к настоящему времени я должен сказать, предложил). Есть две разумные альтернативы, о которых я знаю.

Предложение структуры Entity Framework

Лерман и Миллер в своей книге "Программирование сущности: DbContext, предложил альтернативный метод, который был ближе всего к замене самоконтролирующих сущностей, которые команда EF придумала до сих пор. Он вращается вокруг этого интерфейса:

public interface IObjectWithState
{
    State State { get; set; }
    Dictionary<string, object> OriginalValues { get; set; }
}

Где State есть

public enum State
{
    Added,
    Unchanged,
    Modified,
    Deleted
}

Чтобы этот подход работал правильно, каждый объект должен реализовать интерфейс. Кроме того, для каждого подкласса DbContext требуется ряд методов. Метод заполнения свойства OriginalValues, когда материализуется сущность, и методы синхронизации своего трекера изменений с изменениями, записанными в сущностях, когда они возвращаются в контекст. Это слишком много, чтобы скопировать весь этот код здесь, вы можете найти его в книге, начиная со страницы 102.

Хорошо, если вы реализуете все это, у вас есть самонаблюдения сущностей рода. Это довольно подробно, хотя после его реализации оно "просто сработает". Однако основным недостатком является то, что все пользователи вашего контекста должны установить это свойство State, когда объект добавляется или удаляется. Это сложная ответственность за навязывание клиентского кода!

Breeze

Breeze предлагает полное решение, которое проходит весь путь от DAL в вашей службе до кода javascript на клиенте. Это невероятно удобно и невероятно страшно.

В javascript вы получаете синтаксис типа LINQ:

var query = breeze.EntityQuery
           .from("Customers")
           .where("CompanyName", "startsWith", "A")
           .orderBy("CompanyName");

Это связывается с Breeze EntityManager в коде С#:

var manager = new Breeze.Sharp.EntityManager(serviceName);
var results = await manager.ExecuteQuery(query);

Этот EntityManager в основном представляет собой оболочку вокруг контекста EF. Если все движущиеся части настроены правильно, это фактически привносит контекст EF в ваш javascript, отслеживая изменения, сохраняя изменения и все. Я работаю с ним в одном проекте и действительно, это очень удобно.

Но если вы используете Breeze, это Breeze полностью. Это влияет на все. Изменение схемы базы данных требует изменений в javascript. Это страшно, но к чему вы привыкли. Но если вы хотите делать что-то по-своему, становится очень сложно (хотя и не невозможно) сгибать бриз в соответствии с вашими потребностями. Как жить со своей свекровью. Я думаю, во многих случаях, в конце концов, сочетание Бриз и других образцов становится неизбежным.

Но вам все равно нужно?

Вообще говоря, основной недостаток любого автоматизированного отслеживания отключенных объектов заключается в том, что слишком просто использовать оригинальные объекты объектов для передачи данных. Дело в том, что в большинстве случаев полные сущности содержат гораздо больше данных, чем требует клиент (или разрешено видеть). Использование выделенных тонких DTO может значительно повысить производительность. И, конечно же, они действуют как слой абстракции между DAL и UI/контроллерами.

Да, с DTO мы всегда должны "перекрашивать государство" на стороне сервера. Быть по сему. Это действительно рекомендуемый подход для отключенных сценариев.

Джон Папа, в своем курсе PluralSight по шаблону горячего полотенца для SPA, объясняя Бриз, признает эту проблему. Он предлагает решение с "частичными сущностями". Это решение, но довольно сложное и неуклюжее. И, конечно же, все объекты находятся в основе передачи данных.

Ответ 2

Как упоминалось в одном из предыдущих плакатов, это не поддерживается в EF и является одной из наиболее востребованных функций.

Однако, возможно, если вы захотите использовать библиотеку GraphDiff (или написать что-то подобное себе). Я настоятельно рекомендую вам использовать библиотеку open GraphDiff, чтобы обновлять отключенные графики в EntityFramework.

Просьба ознакомиться с этой статьей от Brent McKendrick за инструкциями о том, как это сделать.

Репозиторий github для этого проекта также можно найти здесь

Из приведенной выше статьи обновление графика связанных объектов так же просто, как в приведенном ниже примере:

using (var context = new TestDbContext())  
{
    // Update the company and state that the company 'owns' the collection contacts, with contacts having associated advertisement options.
    context.UpdateGraph(company, map => map
        .OwnedCollection(p => p.Contacts, with => with
            .AssociatedCollection(p => p.AdvertisementOptions))
        .OwnedCollection(p => p.Addresses)
    );

    context.SaveChanges();
}

Обратите внимание, что этот проект больше не поддерживается, хотя я использовал его (как и многие другие) без каких-либо серьезных проблем.

Ответ 3

Посмотрите на следующие темы: Entity Framework 5 Обновление записи, Обновить отношения при сохранении изменений объектов EF4 POCO. Я чувствую, что он хорошо описывает все возможности и их плюсы и минусы. В основном каждая система отслеживания заставит вас хранить где-то исходную сущность для сравнения или запросить ее снова. Упомянутые объекты автотрассировки делают то же самое - вы должны хранить исходный объект в сеансе или состоянии просмотра, что ухудшит производительность страницы (здесь больше об этом: Объекты самоконтроля и объекты POCO). Что касается меня, я бы просто изменил состояние объекта на модифицированное и давал ef обновить базу данных (так же, как вы делаете это прямо сейчас в методе Put([FromBody]Word word)), и это не только моя перспектива - Entity Framework: обновить связанные объекты.

Многие люди запрашивали такие возможности слияния (http://entityframework.codeplex.com/workitem/864), но пока EF не предоставляет никакой поддержки для этого. Однако некоторые разработчики отмечают, что есть некоторые альтернативы, такие как NHibernate, которые имеют встроенные возможности, такие как http://www.ienablemuch.com/2011/01/nhibernate-saves-your-whole-object.html.

Ответ 4

Да, вы можете, используя следующие библиотеки с низким уровнем воздействия, которые делают то, что вам нужно:

https://github.com/AutoMapper/AutoMapper https://github.com/TylerCarlson1/Automapper.Collection

Обычно я передаю приведенную форму вовлеченных классов клиенту (содержащий только уменьшенный набор столбцов/свойств) по разным причинам.

Обратите внимание, что классы EF находятся в пространстве имен ORM, тогда как приведенные классы находятся в текущем пространстве имен.

Ниже вы можете найти пример кода:

private IMapper map;

private void InitMapper()
{
    map = new MapperConfiguration(cfg => {
        cfg.CreateMap<ORM.SampleSentence, SampleSentence>();
        cfg.CreateMap<ORM.WordForm, WordForm>();
        cfg.CreateMap<ORM.Word, Word>();
        cfg.CreateMap<SampleSentence, ORM.SampleSentence>();
        cfg.CreateMap<WordForm, ORM.WordForm>();
        cfg.CreateMap<Word, ORM.Word>();
        cfg.AddProfile<CollectionProfile>();
    }).CreateMapper();

    EquivilentExpressions.GenerateEquality.Add(new GenerateEntityFrameworkPrimaryKeyEquivilentExpressions<YourDbContext>(map));
}

public async Task<IHttpActionResult> Put([FromBody]Word word)
{
    var dbWord = db.Word.Include(w => w.WordForm).Where(w => w.WordId == word.WordId).First();

    if (dbWord != null)
    {
        InitMapper();
        map.Map(word, dbWord);
        db.SaveChanges();

        var ret = map.Map<Word>(dbWord);

        return Ok(ret);
    }
    else
    {
        return NotFound();
    }

}

Это решение работает хорошо, и EF выпустит обновление, содержащее только те поля, которые имеют фактически измененный.

Примечание. Используя AutoMapper, вы также можете настроить, какие поля нужно обновлять/отображать (также по-разному для каждого направления). Пример: вы хотите, чтобы некоторые поля были "доступны только для чтения" для пользователя, вы можете пометить их с помощью opt.Ignore() в направлении Object- > ORM.Object

    cfg.CreateMap<Word, ORM.Word>()
        .ForMember(dest => dest.WordId, opt => opt.Ignore())