Использование хранимых процедур с Linq To Sql, которые имеют дополнительные параметры

StackOverflow https://stackoverflow.com/questions/1423179

Вопрос

У меня очень большая проблема, и я не могу найти кого-то еще в интернете, которая имеет мою проблему. Я очень надеюсь, что StackOverflow поможет мне ...

Я пишу приложение ASP.NET MVC и использую концепцию репозитория с Linq To Sql в качестве хранилища данных. Все отлично работает в отношении выбора строк из представлений. И отловить очень простые ограничения бизнес-правил. Тем не менее, я столкнулся с проблемой в моих отображениях хранимых процедур для удалений, вставок и обновлений. Позвольте мне объяснить:

Наш администратор БД провел большую работу по внедрению бизнес-логики во все наши хранимые процедуры, чтобы мне не пришлось беспокоиться об этом с моей стороны. Конечно, я делаю базовую проверку, но он управляет целостностью данных и конфликтующими ограничениями дат и т. Д. Проблема, с которой я столкнулся, состоит в том, что все хранимые процедуры (и я имею в виду все) имеют 5 дополнительных параметров (6 для вставок ), которые предоставляют мне информацию. Идея состоит в том, что когда что-то ломается, я могу предложить пользователю соответствующую информацию из нашей базы данных.

Например:

sp_AddCategory(
    @userID INT,
    @categoryName NVARCHAR(100),
    @isActive BIT,
    @errNumber INT OUTPUT,
    @errMessage NVARCHAR(1000) OUTPUT,
    @errDetailLogID INT OUTPUT,
    @sqlErrNumber INT OUTPUT,
    @sqlErrMessage NVARCHAR(1000) OUTPUT,
    @newRowID INT OUTPUT)

Из описанной выше хранимой процедуры первые 3 параметра являются единственными параметрами, которые используются для " Создать " Запись категории. Остальные параметры просто используются, чтобы сообщить мне, что произошло внутри метода. Если бизнес-правило нарушено внутри хранимой процедуры, он НЕ использует ключевое слово SQL RAISEERROR, когда бизнес-правила нарушены. Вместо этого он предоставляет мне информацию об ошибке, используя параметры OUTPUT. Он делает это для каждой хранимой процедуры в нашей базе данных, даже для обновлений и удалений. Все вызовы «Get» выполняются с использованием пользовательских представлений. Все они были протестированы, и идея состояла в том, чтобы облегчить мою работу, поскольку мне не нужно добавлять бизнес-логику, чтобы перехватывать все различные сценарии для обеспечения качества данных.

Как я уже сказал, я использую Linq To Sql, и теперь я столкнулся с проблемой. Проблема в том, что моя " Категория " Объект модели просто имеет 4 свойства: CategoryID, CategoryName, UserId и IsActive. Когда я открыл конструктор и начал сопоставлять свои свойства для вставки, я понял, что для меня нет (простого) способа учесть дополнительные параметры, если я не добавлю их в свой объект Model.

Теоретически, мне хотелось бы сделать следующее:

// note: Repository Methods
public void AddCategory(Category category)
{
    _dbContext.Categories.InsertOnSubmit(category);
}

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

А затем из моего класса CategoryController я просто сделал бы следующее:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(FormCollection collection)
{
    var category = new Category();
    try 
    {
        UpdateModel(category); // simple validation here...

        _repository.AddCategory(category);
        _repository.Save(); // should get error here!!

        return RedirectToAction("Index");
    }
    catch
    {
        // manage friendly messages here somehow... (??)
        // ...

        return View(category);
    }
}

Как лучше всего справиться с этим, используя Linq to Sql? Я (лично) не чувствую, что имеет смысл добавлять все эти дополнительные свойства к каждому объекту модели ... Например, «Get» НИКОГДА не должен иметь ошибок, и я не хочу, чтобы мои методы репозитория возвращали одно тип объекта для вызовов Get, но принимайте объект другого типа для вызовов CUD.

Обновление: мое решение! (1 декабря 2009 г.)

Вот что я сделал, чтобы решить мою проблему. Я избавился от моего метода Save () во всех моих репозиториях. Вместо этого я добавил метод «Update ()» в каждый репозиторий и фактически фиксирую данные в базе данных при каждом вызове CUD (т. Е. Create / Update / Delete).

Я знал, что каждая хранимая процедура имеет одинаковые параметры, поэтому я создал класс для их хранения:

public class MySprocArgs 
{
    private readonly string _methodName;
    public int? Number;
    public string Message;
    public int? ErrorLogId;
    public int? SqlErrorNumber;
    public string SqlErrorMessage;
    public int? NewRowId;

    public MySprocArgs(string methodName)
    {
        if (string.IsNullOrEmpty(methodName))
            throw new ArgumentNullException("methodName");

        _methodName = methodName;
    }

    public string MethodName
    {
        get { return _methodName; }
    }

}

Я также создал MySprocException, который принимает MySprocArgs в своем конструкторе:

public class MySprocException : ApplicationException
{

    private readonly MySprocArgs _args;
    public MySprocException(MySprocArgs args) : base(args.Message)
    {
       _args = args;
    }

    public int? ErrorNumber
    {
        get { return _args.Number; }
    }

    public string ErrorMessage
    {
        get { return _args.Message; }
    }

    public int? ErrorLogId
    {
        get { return _args.ErrorLogId; }
    }

    public int? SqlErrorNumber
    {
        get { return _args.SqlErrorNumber; }
    }

    public string SqlErrorMessage
    {
        get { return _args.SqlErrorMessage; }
    }
}

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

public void AddCategory(Category category)
{
   var args = new MySprocArgs("AddCategory");
   var result = _dbContext.AddWidgetSproc(
                    category.CreatedByUserId,
                    category.Name,
                    category.IsActive,
                    ref args.Number, // <-- Notice use of 'args'
                    ref args.Message,
                    ref args.ErrorLogId,
                    ref args.SqlErrorNumber,
                    ref args.SqlErrorMessage,
                    ref args.NewRowId);

   if (result == -1)
      throw new MySprocException(args);
} 

Теперь из моего контроллера я просто делаю следующее:

[HandleError(ExceptionType = typeof(MySprocException), View = "SprocError")]
public class MyController : Controller
{
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Create(Category category)
    {
        if (!ModelState.IsValid)
        { 
            // manage friendly messages        
            return View(category);
        }

        _repository.AddCategory(category);
        return RedirectToAction("Index");

    } 
}

Хитрость в управлении новым MySprocException заключается в том, чтобы просто перехватить его с помощью атрибута HandleError и перенаправить пользователя на страницу, которая понимает MySprocException.

Надеюсь, это кому-нибудь поможет. :)

Это было полезно?

Решение

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

Но вы можете обрабатывать выходные параметры в LINQ следующим образом.

Добавьте хранимые процедуры, которые вы хотите вызвать, в свой .dbml, используя конструктор.

Назовите свою хранимую процедуру в своем коде

 using (YourDataContext context = new YourDataContext())
 {
    Nullable<int> errNumber = null;
    String errMessage = null;
    Nullable<int> errDetailLogID = null;
    Nullable<int> sqlErrNumber = null;
    String sqlErrMessage = null;
    Nullable<int> newRowID = null;
    Nullable<int> userID = 23;
    Nullable<bool> isActive=true;

    context.YourAddStoredProcedure(userID, "New Category", isActive, ref errNumber, ref errMessage, ref errDetailLogID, ref sqlErrNumber, ref sqlErrMessage, ref newRowID);
 }
scroll top