Нужен совет по сложному сценарию вставки с использованием LinqToSql с шаблоном репозитория

У меня есть таблица User и таблица ClubMember в моей базе данных. Между пользователями и членами клуба существует однозначное сопоставление, поэтому каждый раз, когда я вставляю ClubMember, мне нужно сначала вставить User. Это реализовано с помощью внешнего ключа на ClubMember (UserId REFERENCES User (Id)).

В моем приложении ASP.NET MVC я использую LinqToSql и шаблон репозитория для обработки логики сохраняемости. Как я это реализовал в настоящее время, мои транзакции User и ClubMember обрабатываются отдельными классами репозитория, каждый из которых использует свой собственный экземпляр DataContext.

Это прекрасно работает, если нет ошибок базы данных, но я обеспокоен тем, что у меня останутся потерянные записи User, если какие-либо вставки ClubMember завершатся неудачно.

Чтобы решить эту проблему, я рассматриваю возможность перехода на один DataContext, который я мог бы загрузить с помощью обеих вставок, а затем вызвать DataContext.SubmitChanges() только один раз. Однако проблема заключается в том, что Id для User не назначается до тех пор, пока User не будет вставлено в базу данных, и я не могу вставить ClubMember, пока не узнаю UserId.

Вопросы:

  1. Можно ли вставить User в базу данных, получить Id, затем вставить ClubMember, все как одну транзакцию (которую можно откатить, если что-то пойдет не так с какой-либо частью транзакции)? Если да, то как?

  2. Если нет, могу ли я только вручную удалить все созданные потерянные записи User? Или есть лучший способ?


person devuxer    schedule 21.02.2010    source источник


Ответы (2)


Вы можете использовать System.Transactions.TransactionScope для выполнения всего этого в атомарной транзакции, но если вы используете разные экземпляры DataContext, это приведет к распределенной транзакции, что, вероятно, не то, что вам действительно нужно.

Судя по всему, вы на самом деле неправильно реализуете шаблон репозитория. Репозиторий не должен создавать свой собственный DataContext (или объект подключения, или что-то еще) — эти зависимости должны передаваться через конструктор или общедоступное свойство. Если вы сделаете это, у вас не возникнет проблем с обменом DataContext:

public class UserRepository
{
    private MyDataContext context;

    public UserRepository(MyDataContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");
        this.context = context;
    }

    public void Save(User user) { ... }
}

Используйте тот же шаблон для ClubMemberRepository (или как вы его называете), и это становится тривиальным:

using (MyDataContext context = new MyDataContext())
{
    UserRepository userRep = new UserRepository(context);
    userRep.Save(user);
    ClubMemberRepository memberRep = new ClubMemberRepository(context);
    memberRep.Save(member);
    context.SubmitChanges();
}

Конечно, даже это немного сомнительно. Если у вас есть внешний ключ в вашей базе данных, вам даже не нужно два репозитория, потому что связью управляет Linq to SQL. Создаваемый код должен выглядеть примерно так:

using (MyDataContext context = new MyDataContext())
{
    User user = new User();
    user.Name = "Bob";
    user.ClubMember = new ClubMember();
    user.ClubMember.Club = "Studio 54";

    UserRepository userRep = new UserRepository(context);
    userRep.Save(user);
    context.SubmitChanges();
}

Не возитесь с несколькими репозиториями — позвольте Linq to SQL обрабатывать отношения за вас, для этого и нужны ORM.

person Aaronaught    schedule 21.02.2010
comment
Аарон, отличный ответ, спасибо. Я корю себя за то, что у меня изначально было что-то похожее на ваш третий фрагмент кода, но в ходе отладки я пришел к (неверному) выводу, что это не сработает. Пробую сейчас еще раз, работает отлично. Во-вторых, мои классы в репозитории основаны на NerdDinner, так что я виню Скотта Гу в моей далеко не лучшей реализации :). На самом деле у меня есть задача, чтобы начать обертывать мои экземпляры DataContext операторами using (), и ваш пример дает мне хорошее представление о том, как это сделать. - person devuxer; 22.02.2010

Да, вы можете сделать это за одну транзакцию. Используйте объект TransactionScope, чтобы начать и зафиксировать транзакцию (и откат, если, конечно, есть ошибка)

person Mike Mooney    schedule 21.02.2010