C# EF Core Procedure use with Class - c#

I tried to use EF Core to execute a procedure, so I define a class as ACT, then use ACT class to catch procedure output, but I got this error:
Return : The required column 'A02' was not present in the results of a 'FromSql' operation.
I already know class fields and procedure have match field, but I have many procedures to use, I do I have to create a new class for each procedure ?
ACT.cs
namespace E-shop.Shared
{
public class ACT
{
[Key]
public string A01 { get; set; } = string.Empty;
public string S01 { get; set; } = string.Empty;
public string A02 { get; set; } = string.Empty;
public string A03 { get; set; } = string.Empty;
public string A04 { get; set; } = string.Empty;
public string A05 { get; set; } = string.Empty;
public int A06 { get; set; }
public int A07 { get; set; }
public int A08 { get; set; }
public DateTime A09 { get; set; }
public int A10 { get; set; }
public int? A11 { get; set; }
public int? A12 { get; set; }
public int? A13 { get; set; }
}
}
Procedure
ALTER PROCEDURE [dbo].[usp_Sel_SAcc1]
(#A01 Varchar(50))
AS
SELECT A01
FROM ACT
WHERE A01 = #A01
The method
public void CheckAccount (string Email)
{
string ProcdureName = "usp_Sel_SAcc1";
var parameter = new SqlParameter("#A01", Email);
var user = Model1_context.ACT
.FromSqlInterpolated($"EXECUTE {ProcdureName} {parameter}")
.ToList();
Console.WriteLine(user.ToString());
}
=======Update 2023/02/13 =========
Thanks every one reply!
I think I need to describe the problem in more detail!!
I got many procedure in our-project Sql, like picture at under.
Every procedure select data is not the same, But it most of use ACT Class, ACT Class is the class corresponding to the database table.
And that procedure most of them also use this table, So I wanna find a way to just use one class to catch many procedure return.
procedures example:
SACC3
ALTER Procedure [dbo].[usp_Sel_SAcc3]
(
#A01 Varchar(50)
)
AS
SELECT ACT.A04,ACT.A05,ACT.A07,ACT.A09,ACT.A03,Store.S02,Store.S09,Store.S10,Store.S13,ACT.A06,ACT.S01 FROM ACT INNER JOIN Store ON ACT.S01 =Store.S01 WHERE ACT.A01 = #A01
SAcc7
ALTER Procedure [dbo].[usp_Sel_SAcc7]
(
#S01 Char(8),
#A01 Varchar(50)
)
AS
SELECT A03,A04,A05 FROM ACT WHERE S01 = #S01 and A01=#A01

Why would you have a stored procedure that returns one column from a table, selecting by that same column? If it is more a case of selecting a record using the AO1 value then the Stored Proc would be more like:
CREATE PROCEDURE [dbo].[usp_SelectBySAcc1]
(#A01 Varchar(50))
AS
SELECT A01, A02, A03, S01, ... /* fill in all applicable fields */
FROM ACT
WHERE A01 = #A01
Then EF can populate your desired entity from the result set coming back from the stored proc. With .Net Core if any of those string columns are null-able, you will want to declare the fields as string? to avoid compiler warnings.
Your code will also likely not work as you expect. ToList() intends to load a collection of matching entities. If you are loading an entity by Key and expect just one row (or zero rows if not found) then use Single or SingleOrDefault.
var user = Model1_context.ACT
.FromSqlInterpolated($"EXECUTE {ProcdureName} {parameter}")
.Single();
Ultimately the point of EF is to not need to write SQL or stored procedures. Getting an ACT record by A01 is as simple as:
var user = Model1_context.ACT
.Single(x => x.A01 == Email);
EF will write the SQL needed to fetch the record. If you just want to check if a record exists:
var userExists = Model1_context.ACT
.Any(x => x.A01 == Email);

Related

Using a stored procedure to retrieve all or single user from AspNetUsers table

I see a lot of information on ASP.Net Core Identity storing DATA with the stored procedure and I tried and use the same method to retrieve / Get all or a single user detail from AspNetUsers table in SQL, but did not succeed.
below is my code.
My Stored Procedure
alter PROCEDURE [dbo].[spEmployeeDetails]
#EmployeeID varchar(50) = ''
AS
BEGIN
IF(#EmployeeID !='')
BEGIN
SELECT * FROM AspNetUsers WHERE EmployeeId like #EmployeeID + '%'
END
ELSE
BEGIN
SELECT * FROM AspNetUsers
END
END
Model class
public class AppUsers:IdentityUser
{
public string EmployeeId { get; set; }
public string FName { get; set; }
public string LName { get; set; }
public string Nationality { get; set; }
public string PositionTitle { get; set; }
public string Department { get; set; }
public string DepartmentCode { get; set; }
public string PosCode { get; set; }
public string Grade { get; set; }
}
service class
public AppUsers GetaUser(string id)
{
var getuser = context.AppUsers.FromSqlRaw($"spEmployeeDetails {id}").ToList();
return getuser.FirstOrDefault();
}
the error I am getting is
InvalidOperationException: 'FromSqlRaw' or 'FromSqlInterpolated' was called with non-composable SQL and with a query composing over it. Consider calling 'AsEnumerable' after the method to perform the composition on the client side.
I tried this code also but no luck
IEnumerable<AppraisalUsers> objd = context.AppraisalUsers.FromSqlRaw($"spEmployeeDetails {id}").AsEnumerable<AppraisalUsers>();
can anyone help me to retrieve a user from AspNetUsers table generated by Identity framework
I am working on Core3.1
Instead of FromSqlRaw you should use FromSqlInterPolated to stop sql injection attacks.
You can try adding the SQL Parameter name like so
context.AppUsers.FromSqlInterPolated ($"exec spEmployeeDetails #EmployeeID={id}")
You could also write this as pure ef core code as
return context.AppUsers.FirstOrDefault(e => e.EmployeeId == id);

Checking for a Value in a Table and Updating It via a RestAPI

I wanted to check for a value in a table and update it if it fits certain conditions but I don't think I'm doing it right:
public void UpdateUsertoActive(string email)
{
var token = ApiLogin();
string filter = $"Email_Address = '{email}'";
var contactstatus = new List<string>
{"Contact_Status_ID"};
var activestatus = _ministryPlatformRest.UsingAuthenticationToken(token).Search<MpMyContact>(filter, contactstatus);
if (activestatus.Equals(2))
{
activestatus = _ministryPlatformRest.UsingAuthenticationToken(token).UpdateRecord("dbo.Contacts", GetContactIdByEmail(email), contactstatus);
}
else
{//do nothing}
}
I want to check for the Contact_Status_ID; if it is == 2 then change it to 1 else do nothing.
This is the Contact Model from which the Contact_Status_ID comes from:
namespace MinistryPlatform.Translation.Models
{
[MpRestApiTable(Name = "Contacts")]
public class MpMyContact
{
public int? Address_ID { get; set; }
public string Address_Line_1 { get; set; }
public string Address_Line_2 { get; set; }
public int? Contact_Status_ID { get; set; }
}
}
You could make this a lot simpler by swapping out all the code to find the status and just issue an update statement in a procedure. Something like this. Essentially what your code is doing is running a query to see if the row exists in that status. If it does, run a second query to update it. It is much less resource intensive to just update it.
create procedure UpdateUsertoActive
(
#EmailAddress varchar(500)
) as
set nocount on;
Update YourTable
set Contact_Status_ID = 2
where Contact_Status_ID = 1
and Email_Address = #EmailAddress

Dapper with postgresql error (sequence contains more than one element)

I am getting this error when I run an integration test against my Postgresql DB using a stored procedure.
Result Message: System.InvalidOperationException : Sequence contains more than one element
Here is the repo file:
public card_view SelectView(int card_id)
{
using (var connection = new NpgsqlConnection(ConfigurationSettings.GetConnectionString()))
{
var p = new DynamicParameters();
p.Add("#card_id", card_id);
using (var multi = connection.QueryMultiple("f_card_select_view", p, commandType: CommandType.StoredProcedure))
{
card_view view = multi.Read<card_view>().Single();
view.Categories = multi.Read<category>().ToList();
view.Modifiers = multi.Read<card_modifier_view>().ToList();
return view;
}
}
}
The card test file:
[Test]
public void SelectViewTest()
{
var repo = new CardRepository();
var result = repo.SelectView(31); // witch
Assert.AreEqual(2, result.Categories.Count);
Assert.AreEqual(2, result.Modifiers.Count);
}
Cardview file:
public class card_view
{
public int card_id { get; set; }
public int cardset_id { get; set; }
public string cardset_title { get; set; }
public string image_path { get; set; }
public string cardset_name { get; set; }
public int card_cost { get; set; }
public List<card_modifier_view> Modifiers { get; set; }
public List<category> Categories { get; set; }
}
cardmodifierview file:
public class card_modifier_view
{
public int card_modifier_id { get; set; }
public int card_id { get; set; }
public int modifier_type_id { get; set; }
public int? modifier_value { get; set; }
public string instruction_text { get; set; }
public string modifier_type_name { get; set; }
}
The DB function (The original SQL was T-SQL and I have done my best to translate it into regular SQL for postgres.)
CREATE FUNCTION f_card_select_view (card_id int)
RETURNS TABLE(card_id bigint, modifier_type_id integer,
instruction_text integer, modifier_type_name integer, card_modifier character varying, modifier_type character varying)
AS $$
SELECT card_id,cardset.cardset_id,card_title,image_path,card_cost,cardset_name
FROM card
INNER JOIN cardset ON card.cardset_id = cardset.cardset_id
WHERE card_id = #card_id;
SELECT card.category_id,category_name
FROM category card
INNER JOIN card_category ON card.category_id = card_category.category_id
WHERE card_category.card_id = #card_id;
SELECT f_card_modifier_selectby_card_id (#card_id);
$$ LANGUAGE sql;
Here is f_card_modifier_selectby_card_id:
CREATE FUNCTION f_card_modifier_selectby_card_id(card_id int)
RETURNS TABLE(
card_id bigint,
modifier_type_id int,
instruction_text int,
modifier_type_name int,
card_modifier varchar,
modifier_type varchar
)
AS $$
SELECT
card_modifier_id,
card_id,
card_modifier.modifier_type_id,
modifier_value,
instruction_text,
modifier_type_name
FROM card_modifier INNER JOIN modifier_type ON card_modifier.modifier_type_id = modifier_type.modifier_type_id
WHERE card_id = card_id
$$ LANGUAGE sql;
If the error is Sequence contains more than one element, then the problem is that your first query (where you have the .Single()) is returning more than one row. I can't tell you why that is, but you need to try running:
SELECT card_id,cardset.cardset_id,card_title,image_path,card_cost,cardset_name
FROM card
INNER JOIN cardset ON card.cardset_id = cardset.cardset_id
WHERE card_id = #card_id;
(with your expected #card_id) in your SQL toolkit to see what happens. Or better: call the stored procedure itself - presumably via:
EXEC f_card_select_view {your id here}
It looks like your statement brings back a join containing multiple records for card_view or 0.
In other frameworks certainly you can use SingleOrDefault() to allow for 0 records. Note this will still error for multiple records.
If its getting multiple records you need to establish if this is correct and rework it to be a collection like you have done with .ToList() or correct your data in the database and possibly your keys.

Entity Framework stored procedure single result set

I am working with a MySQL stored procedure and trying to retrieve the single result set using EF6. My stored procedure contains the simple select statement and I have mapped it into my model. Below is the class generated by EF to map to my stored procedure
public partial class usp_aggregatedLogs_Result
{
}
It's an empty class generated by EF. I have added the properties to this class to map it to the result set returned by stored procedure.
public partial class usp_aggregatedLogs_Result
{
public int AccountId { get; set; }
public string AccountName { get; set; }
public string ProjectId { get; set; }
public string ProjectName { get; set; }
public string SystemId { get; set; }
public string SystemName { get; set; }
public string ParameterId { get; set; }
public string ParameterName { get; set; }
public Nullable<System.DateTime> TimeStamp { get; set; }
public long LogId { get; set; }
public string Type { get; set; }
}
Below is the code generated by EF in the DBContext Class
public virtual ObjectResult<usp_aggregatedLogs_Result> usp_aggregatedLogs(Nullable<System.DateTime> dateFrom, Nullable<System.DateTime> dateTo)
{
var dateFromParameter = dateFrom.HasValue ?
new ObjectParameter("DateFrom", dateFrom) :
new ObjectParameter("DateFrom", typeof(System.DateTime));
var dateToParameter = dateTo.HasValue ?
new ObjectParameter("DateTo", dateTo) :
new ObjectParameter("DateTo", typeof(System.DateTime));
return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction<usp_aggregatedLogs_Result>("usp_aggregatedLogs", dateFromParameter, dateToParameter);
}
I assume that calling this function should return me the result set returned by stored procedure. I am calling it this way
List<usp_aggregatedLogs_Result> ResultList= obj.usp_aggregatedLogs(DateFrom, DateTo).ToList();
I receive the result in my ResultList. I receive 43 objects which is correct as my stored procedure returns 43 rows. But I get no values for the properties of these objects. all properties values are either set to 0 or null.it seems like my ResultList objects are not initialized.
I don't know how to properly call my stored procedure and retrieve its result set into my application.
Please help.
I use a bit of a trick for it.
Create a view that has the same data structure as data returned by your procedure and add it to Your edmx. It doesnt have to be anything logical. Can be like:
select 1 as AccountId, 'ABC' as AccountName , (...) from x;
as long as it has correct data types. But if Your procedure only filters some data that You can put into a view I recommend doing a meaningfull view.
When mapping Your procedure in EF choose mapped view as return type.
You don't have to return the view from the procedure. You can return any data as long as it has matching structure with the view.
In C# Your procedure will now return a collection of view items.
This worked for me on MSSQL so please let me know if You managed to kick it off on MySQL.

Mapping stored procedures to views

I have the problem with the mapping a stored procedure to an EF entity which is represented by the view in the database.
If I try to call, for example, an .Add method - get the error
Too many parameters ...
I know/understand, that EF wants all parameters of the entity except keys (+computed) in the mapped Add - stored procedure. But in the case of "entity = view", I want to post as stored procedure parameters only some set of EF entity fields, which I have in db table (one field set in the case of insert, another set in the case of update, third set in the case of delete).
How to do this "right"? In .edmx this (mapping via graphic interface) works perfectly but I need to realize this behavior in the code-first by hands..
Example:
View in DB ..
CREATE VIEW vDepartment
AS
SELECT
d.*,
dp.Code as ParentCode, dp.SName as ParentSName,
dp.Name as ParentName
FROM
Department d
LEFT OUTER JOIN
Department dp ON d.ParentID = dp.ID
EF entity
public partial class vDepartment
{
public long ID { get; set; }
public Nullable<long> ParentID { get; set; }
public string Code { get; set; }
public string SName { get; set; }
public string Name { get; set; }
public Nullable<System.DateTime> CloseDate { get; set; }
public string ParentCode { get; set; }
public string ParentSName { get; set; }
public string ParentName { get; set; }
}
Mapping ..
modelBuilder.Entity<vDepartment>().MapToStoredProcedures(s =>
{
s.Update(u => u.HasName("udp_Department_upd"));
s.Delete(d => d.HasName("udp_Department_del"));
s.Insert(i => i.HasName("udp_Department_ins").Result(r => r.ID, "NewID"));
});
Insert stored procedure in database:
CREATE PROC [dbo].[udp_Department_ins]
#ParentID BIGINT,
#Code NVARCHAR(20),
#SName NVARCHAR(50),
#Name NVARCHAR(100),
#CloseDate DATE
AS
BEGIN
DECLARE #NewID bigint;
INSERT INTO Department...
SELECT #NewID AS NewID;
END;
you can do:
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public string ParentSName { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public string ParentName { get; set; }
to prevent ParentSName and ParentName to be offered as parameters to your stored procedure. Note that this does not mean these columns have to be actual computed columns in your database. Effectively it marks them as not updateble/readonly to EF.
I have not found a direct way to tell the mapping to ignore a parameter for a specific stored procedure, like in the edmx designer.
And do remember to add the computed columns as output of your stored procedure. This may or may not be required if these values change in the database on update.
s.Insert(i => i.HasName("udp_Department_ins").Result(r => r.ID, "NewID").Result(r => r.ParentName , "ParentName")

Categories

Resources