У меня есть контроллер, указанный здесь, который должен вызывать метод Dapper, однако я не могу заставить его выполниться. Он не выдает никаких исключений и перенаправляется при попытке поймать, однако процедура, похоже, не выполняется, поскольку новые пользователи никогда не создаются.
Настройка соединения работает с помощью простых встроенных методов .Query
, однако, как только я пытаюсь сделать это с помощью хранимой процедуры, происходит сбой.
Модель:
public class User
{
public int UserID { get; set; }
public string Username { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Boolean ActiveB { get; set; }
}
Вид:
@model ScaleBase.Models.FullUser
@{
ViewData["Title"] = "Create";
}
<h2>Create</h2>
<h4>User</h4>
<hr />
<div class = "row">
<div class = "col-md-4">
<form asp-action = "Create">
<div asp-validation-summary = "ModelOnly" class = "text-danger"></div>
<div class = "form-group">
<label asp-for = "OrganisationID" class = "control-label"></label>
<input asp-for = "OrganisationID" class = "form-control" />
<span asp-validation-for = "OrganisationID" class = "text-danger"></span>
</div>
<div class = "form-group">
<label asp-for = "ClientID" class = "control-label"></label>
<input asp-for = "ClientID" class = "form-control" />
<span asp-validation-for = "ClientID" class = "text-danger"></span>
</div>
<div class = "form-group">
<label asp-for = "TeamID" class = "control-label"></label>
<input asp-for = "TeamID" class = "form-control" />
<span asp-validation-for = "TeamID" class = "text-danger"></span>
</div>
<div class = "form-group">
<label asp-for = "Email" class = "control-label"></label>
<input asp-for = "Email" class = "form-control" />
<span asp-validation-for = "Email" class = "text-danger"></span>
</div>
<div class = "form-group">
<label asp-for = "Username" class = "control-label"></label>
<input asp-for = "Username" class = "form-control" />
<span asp-validation-for = "Username" class = "text-danger"></span>
</div>
<div class = "form-group">
<label asp-for = "Password" class = "control-label"></label>
<input asp-for = "Password" type = "password" class = "form-control" />
<span asp-validation-for = "Password" class = "text-danger"></span>
</div>
<div class = "form-group">
<label asp-for = "FirstName" class = "control-label"></label>
<input asp-for = "FirstName" class = "form-control" />
<span asp-validation-for = "FirstName" class = "text-danger"></span>
</div>
<div class = "form-group">
<label asp-for = "LastName" class = "control-label"></label>
<input asp-for = "LastName" class = "form-control" />
<span asp-validation-for = "LastName" class = "text-danger"></span>
</div>
<div class = "form-group">
<div class = "checkbox">
<label>
<input asp-for = "ActiveB" /> @Html.DisplayNameFor(model => model.ActiveB)
</label>
</div>
</div>
<div class = "form-group">
<input type = "submit" value = "Create" class = "btn btn-default" />
</div>
</form>
</div>
</div>
Контроллер:
public ActionResult Create(IFormCollection collection)
{
try
{
DynamicParameters parameters = new DynamicParameters();
parameters.Add("@Organisation", collection["OrganisationID"]);
parameters.Add("@ClientID1", collection["ClientID"]);
parameters.Add("@Team", collection["TeamID"]);
parameters.Add("@Email", collection["Email"]);
parameters.Add("@UserName", collection["UserName"]);
parameters.Add("@Password", collection["Password"]);
parameters.Add("@FirstName", collection["FirstName"]);
parameters.Add("@LastName", collection["LastName"]);
var affectedRows = _dapperRepo.CreateUser(parameters);
return RedirectToAction(nameof(Index));
}
catch (Exception)
{
throw;
}
}
Шикарный репозиторий:
public async Task<User> CreateUser(DynamicParameters parameters)
{
using (IDbConnection conn = Connection)
{
string sproc = "EXEC sproc_NewUser @Organisation, @Client1, @Team, @Email @UserName, @Password, @FirstName, @LastName";
conn.Open();
var result = await conn.QueryAsync(sproc, parameters, commandType: CommandType.StoredProcedure);
return result.FirstOrDefault();
}
}
Хранимая процедура:
BEGIN TRY
BEGIN TRANSACTION NewUser
DECLARE @salt UNIQUEIDENTIFIER = NEWID()
INSERT INTO [dbo].[User] (Username, Email, FirstName, LastName, Password, Salt, Active)
VALUES (@UserName, @Email, @FirstName, @LastName, HASHBYTES('SHA2_512', @Password+CAST(@salt AS NVARCHAR(36))), @salt, 1)
INSERT INTO [dbo].[UserOrganisations] (UserID, OrganisationID)
VALUES (IDENT_CURRENT('User'), @Organisation)
INSERT INTO [dbo].[UserClients] (UserID, ClientID)
VALUES (IDENT_CURRENT('User'), @Client1)
IF @Client2 IS NOT NULL
BEGIN
INSERT INTO [dbo].[UserClients] (UserID, ClientID)
VALUES (IDENT_CURRENT('User'), @Client2)
END
IF @Client3 IS NOT NULL
BEGIN
INSERT INTO [dbo].[UserClients] (UserID, ClientID)
VALUES (IDENT_CURRENT('User'), @Client3)
END
IF @Client4 IS NOT NULL
BEGIN
INSERT INTO [dbo].[UserClients] (UserID, ClientID)
VALUES (IDENT_CURRENT('User'), @Client4)
END
IF @Client5 IS NOT NULL
BEGIN
INSERT INTO [dbo].[UserClients] (UserID, ClientID)
VALUES (IDENT_CURRENT('User'), @Client5)
END
INSERT INTO [dbo].[UserTeams] (UserID, TeamID)
VALUES (IDENT_CURRENT('User'), @Team)
INSERT INTO [dbo].[UserPermission] (UserID, HolidayCount, HolidayUsed, TemplateID, ConfigState1, ConfigState2, ConfigState3, ConfigState4, ConfigState5)
VALUES (IDENT_CURRENT('User'), @Holiday, 0, 1, 255, null, null, null, null)
INSERT INTO [dbo].[UserTime] (UserID, Scale, StartTime, EndTime)
VALUES (IDENT_CURRENT('User'), 1, @StartTime, @EndTime)
COMMIT TRANSACTION NewUser
PRINT 'Success'
SELECT
[UserID], [Username], [Email], [Firstname], [Lastname], [Active]
FROM
[User]
WHERE
[UserID] = IDENT_CURRENT('User')
END TRY
BEGIN CATCH
IF (@@TRANCOUNT > 0)
BEGIN
ROLLBACK TRANSACTION NewUser
PRINT 'Failed'
END
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() AS ErrorState,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage
RETURN 'Error'
END CATCH
Да я тоже так делаю! К сожалению, это не так просто, у меня есть другие методы, использующие этот DbConnection, и все они работают нормально, по общему признанию, они являются встроенным SQL, работающим с методом Query. Но я не уверен, в чем разница?
SQL Server 2016
Кажется, у вас есть ClientID1 по сравнению с Client1, вы уверены, что передаете правильные значения?
@HansKefing, я только что исправил это и перестроил, однако, похоже, это не решает проблему. Я также дважды проверил параметры от хранимой процедуры до FormCollection, однако похоже, что проблема все еще остается.
В стороне: вместо того, чтобы использовать Ident_Current()
для многократного получения значений, на которые может повлиять любой сеанс или область действия, вам будет лучше захватить значение Scope_Identity()
в переменной сразу после insert
в dbo.User
и использовать это значение после этого.
@HABO, да, ты совершенно прав. Я должен добавить это, как только я заработаю!
А sproc отлично работает из SSMS?
@CaiusJard, это действительно так.
Предполагается, что хранимые процедуры возвращают целочисленные значения, но ваши возвращают строку. Если вы исправите его так, чтобы он возвращал 1 в конце попытки (успех) и возвращал 0 в конце th catch (неудача), что dapper сообщает о возвращаемом значении?
(Добавьте параметр типа Direction.ReturnValue — см. stackoverflow.com/questions/44161310/…)
Другая сторона мысли; возможно, сделать возвращаемое значение ошибки отличным от 0, так как это значение по умолчанию для С# - вы не хотели бы, чтобы dapper по умолчанию имел значение 0, даже не запуская sproc и вводя вас в заблуждение, что 0 возвращается из sproc
При использовании commandType: CommandType.StoredProcedure
вам нужно указать только имя хранимой процедуры, поэтому вместо
string sproc = "EXEC sproc_NewUser @Organisation, @Client1, @Team, @Email @UserName, @Password, @FirstName, @LastName";
Просто пиши
string sproc = "sproc_NewUser";
К сожалению, это не решает проблему. По сути, изменений нет, после отправки формы я все еще получаю перенаправление, но пользователь не создается.
Вы получаете какой-либо результат? Вы подтвердили, что ваша процедура не завершилась откатом транзакции и возвратом ошибки? Если ошибки не регистрируются в базе данных, вы можете удалить try...catch
в хранимой процедуре и просто сделать это на стороне С#. Эмпирическое правило для исключений состоит в том, чтобы выбрасывать как можно раньше, но ловить только тогда, когда вы действительно можете что-то сделать с ошибкой (и что-то может быть повторной попыткой, журналом, отображением удобного для пользователя сообщения об ошибке и т. д.) - в зависимости от самого исключения).
Вместо использования QueryAsync
используйте ExecuteAsync
, который вернет количество строк, измененных командой.
Также вы не ожидаете своего асинхронного вызова. Это плохая практика. Попробуй это:
var affectedRows = await _dapperRepo.CreateUser(parameters);
а также
var result = await conn.ExecuteAsync(sproc, parameters, commandType: CommandType.StoredProcedure);
Вы можете обратиться к этому: https://dapper-tutorial.net/async#executeasync
Только что построил это, и кажется, что это тоже не работает.
Укажите ВСЕ параметры, которые перечислены в вашей хранимой процедуре. @Client2, @ClientID3
ect вызовет ошибку необъявленных параметров.
Изменение реализации этого на .ExecuteAsync
и выполнение в контроллере, похоже, решает эту проблему.
public Async Task<IActionResult> Create(IFormCollection collection)
{
try
{
DynamicParameters parameters = new DynamicParameters();
parameters.Add("@Organisation", collection["OrganisationID"]);
parameters.Add("@ClientID1", collection["ClientID"]);
parameters.Add("@Team", collection["TeamID"]);
parameters.Add("@Email", collection["Email"]);
parameters.Add("@UserName", collection["UserName"]);
parameters.Add("@Password", collection["Password"]);
parameters.Add("@FirstName", collection["FirstName"]);
parameters.Add("@LastName", collection["LastName"]);
var affectedRows = _dapperRepo.CreateUser(parameters);
using (IDbConnection conn = Connection)
{
string sproc = "EXEC sproc_NewUser @Organisation, @Client1, @Team, @Email @UserName, @Password, @FirstName, @LastName";
conn.Open();
var result = await conn.QueryAsync(sproc, parameters, commandType: CommandType.StoredProcedure);
var result2 = result.FirstOrDefault();
}
return RedirectToAction(nameof(Index));
}
catch (Exception)
{
throw;
}
}
Какую систему БД вы используете? SQLS? Вы уверены, что БД, которую вы просматриваете, и БД, которую модифицирует ваш код, это одна и та же БД? Я блокирую себя каждый раз, когда редактирую dev db в коде и смотрю вживую, говоря: «Почему здесь нет моего значения?!?»