Как удалить записи из дочерней коллекции в LINQ to SQL? - PullRequest
2 голосов
/ 23 мая 2009

У меня есть две таблицы в моей базе данных, связанные внешними ключами: Page (PageId, другие данные) и PageTag (PageId, Tag). Я использовал LINQ для генерации классов для этих таблиц, со страницей в качестве родителя и тегом в качестве дочерней коллекции (отношение один ко многим). Есть ли способ пометить записи PageTag для удаления из базы данных из класса Page?

Быстрая очистка:

Я хочу, чтобы дочерние объекты были удалены, когда родительский DataContext вызывает метод SubmitChanges (), а не раньше. Я хочу, чтобы TagString вел себя точно так же, как и любые другие свойства объекта Page.

Я хотел бы включить код, подобный следующему:

Page page = mDataContext.Pages.Where(page => page.pageId = 1);
page.TagString = "new set of tags";

//Changes have not been written to the database at this point.

mDataContext.SubmitChanges();

//All changes should now be saved to the database.

Вот моя ситуация в деталях:
Чтобы упростить работу с коллекцией тегов, я добавил в объект Page свойство, которое обрабатывает коллекцию тегов как строку:

public string TagString {
    get {
        StringBuilder output = new StringBuilder();
        foreach (PageTag tag in PageTags) {
            output.Append(tag.Tag + " ");
        }

        if (output.Length > 0) {
            output.Remove(output.Length - 1, 1);
        }

        return output.ToString();
    }
    set {
        string[] tags = value.Split(' ');
        PageTags.Clear();
        foreach (string tag in tags) {
            PageTag pageTag = new PageTag();
            pageTag.Tag = tag;
            PageTags.Add(pageTag);
        }
    }
}

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

Проблема, с которой я сталкиваюсь, заключается в том, что эта строка:

PageTags.Clear();

Фактически не удаляет старые теги из базы данных при отправке изменений.

Оглядываясь вокруг, можно сказать, что "правильный" способ удаления объектов заключается в вызове метода DeleteOnSubmit класса контекста данных. Но у меня, похоже, нет доступа к классу DataContext из класса Page.

Кто-нибудь знает способ пометить дочерние элементы для удаления из базы данных из класса Page?

Ответы [ 7 ]

6 голосов
/ 24 мая 2009

После еще одного исследования я считаю, что мне удалось найти решение. Маркировка объекта для удаления при его удалении из коллекции контролируется параметром DeleteOnNull атрибута Association.

Этот параметр имеет значение true, когда отношения между двумя таблицами помечены каскадом OnDelete.

К сожалению, нет способа установить этот атрибут из конструктора, и нет способа установить его из частичного класса в файле * DataContext.cs. Единственный способ установить его без включения каскадного удаления - это вручную отредактировать файл * DataContext.designer.cs.

В моем случае это означало поиск ассоциации страницы и добавление свойства DeleteOnNull:

[Association(Name="Page_PageTag", Storage="_Page", ThisKey="PageId", OtherKey="iPageId", IsForeignKey=true)]
public Page Page
{
    ...
}

И добавление атрибута DeleteOnNull:

[Association(Name="Page_PageTag", Storage="_Page", ThisKey="PageId", OtherKey="iPageId", IsForeignKey=true, DeleteOnNull = true)]
public Page Page
{
    ...
}

Обратите внимание, что атрибут необходимо добавить в свойство Page класса класса PageTag, а не наоборот.

Смотри также:
Бет Масси - LINQ to SQL и отношения «один ко многим»
Дейв Брэйс - LINQ to SQL: DeleteOnNull

1 голос
/ 24 мая 2009

Извините, мой плохой. Это не сработает.

Похоже, вам нужно делать это в своем хранилище, а не в своем классе Page. Там у вас есть доступ к исходному контексту данных.

Существует способ «прикрепить» исходный контекст данных, но к тому времени, когда вы это сделаете, он уже станет запахом кода.

0 голосов
/ 24 мая 2009

Образец кода большего размера выложен по запросу Роберта Харви:

Файл DataContext.cs:

namespace MyProject.Library.Model
{
    using Tome.Library.Parsing;
    using System.Text;

    partial class Page
    {
        //Part of Robert Harvey's proposed solution.
        MyDataContext mDataContext = new TomeDataContext();

        public string TagString {
            get {
                StringBuilder output = new StringBuilder();
                foreach (PageTag tag in PageTags) {
                    output.Append(tag.Tag + " ");
                }

                if (output.Length > 0) {
                    output.Remove(output.Length - 1, 1);
                }

                return output.ToString();
            }
            set {
                string[] tags = value.Split(' ');
                //Original code, fails to mark for deletion.
                //PageTags.Clear();

                //Robert Harvey's suggestion, thorws exception "Cannot remove an entity that has not been attached."
                foreach (PageTag tag in PageTags) {
                    mDataContext.PageTags.DeleteOnSubmit(tag);
                }

                foreach (string tag in tags) {
                    PageTag PageTag = new PageTag();
                    PageTag.Tag = tag;
                    PageTags.Add(PageTag);
                }
            }
        }

        private bool mIsNew;
        public bool IsNew {
            get {
                return mIsNew;
            }
        }

        partial void OnCreated() {
            mIsNew = true;
        }

        partial void OnLoaded() {
            mIsNew = false;
        }
    }
}

Методы репозитория:

public void Save() {
    mDataContext.SubmitChanges();
}

public Page GetPage(string pageName) {
    Page page =
        (from p in mDataContext.Pages
        where p.FileName == pageName
        select p).SingleOrDefault();

    return page;
}

Использование:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(string pageName, FormCollection formValues) {
    Page updatedPage = mRepository.GetPage(pageName);

    //TagString is a Form value, and is set via UpdateModel.
    UpdateModel(updatedPage, formValues.ToValueProvider());
    updatedPage.FileName = pageName;

    //At this point NO changes should have been written to the database.

    mRepository.Save();

    //All changes should NOW be saved to the database.

    return RedirectToAction("Index", "Pages", new { PageName = pageName });
}
0 голосов
/ 24 мая 2009

Аарон:

Добавьте член DataContext в ваш частичный класс PageTag.

partial class PageTag 
{ 
    DataClassesDataContext myDataContext = new DataClassesDataContext(); 

    public string TagString { 

.. и т.д.

0 голосов
/ 24 мая 2009

Aaron,

По-видимому, вы должны циклически проходить через свои записи PageTag, вызывая DeleteOnSubmit для каждой из них. Linq to SQL должен создать агрегированный запрос для одновременного удаления всех записей при вызове SubmitChanges, поэтому накладные расходы должны быть минимальными.

заменить

PageTags.Clear();

с

foreach (PageTag tag in PageTags)
    myDataContext.DeleteOnSubmit(tag);
0 голосов
/ 24 мая 2009

Это одна из тех областей, где картографическое ИЛИ может стать немного волосатым. Предоставление этого свойства TagString делает вещи немного более удобными, но в долгосрочной перспективе оно запутывает то, что действительно происходит, когда кто-то использует свойство TagString. Скрывая тот факт, что вы выполняете модификацию данных, кто-то может очень легко прийти и установить TagString, не используя вашу сущность Page в рамках DataContext, что может привести к некоторым трудным для поиска ошибкам.

Лучшим решением было бы добавить свойство Tags в классе Page с помощью дизайнера модели L2S и потребовать, чтобы PageTags редактировались непосредственно в свойстве Tags, в рамках DataContext. Сделайте свойство TagString доступным только для чтения, чтобы его можно было генерировать (и при этом обеспечить некоторое удобство), но устраните путаницу и трудности с настройкой этого свойства. Такое изменение проясняет намерение и делает очевидным, что происходит и что требуется потребителям объекта Page, чтобы это произошло.

Поскольку теги являются свойством объекта Page, если он присоединен к DataContext, любые изменения в этой коллекции будут правильно инициировать удаление или вставку в базу данных в ответ на вызовы Remove или Add.

0 голосов
/ 23 мая 2009

Есть ли у вас связь в диаграмме сущностей Linq to SQL, связывающая таблицы Page и PageTags? Если вы этого не сделаете, вот почему вы не можете видеть класс PageTags из класса Page.

Если для внешнего ключа в таблице базы данных PageTags задано значение Allow Nulls, Linq to SQL не создаст ссылку при перетаскивании таблиц в конструктор, даже если вы создали связь на SQL Server.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...