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")
Related
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);
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);
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.
Is it possible to return custom table from SP . I'm using EF code first approach.
This is my SP
CREATE PROCEDURE [dbo].[GetUserNotification]
#ToUserId INT
AS
BEGIN
select TU.FullName as ToUserName,
FU.FullName as FromUserName,
N.NotificationClickType,
N.TemplateId,
J.Title as JobTitle,
J.Identifier as JobIdentifier,
B.Identifier as BidIdentifier
from Notification N
inner Join AppUser TU on TU.Identifier = N.ToUserId
inner Join AppUser FU on FU.Identifier = N.FromuserId
inner Join Bid B on B.Identifier = N.NotificationBidId
inner Join Job J on J.Identifier = B.JobId
where N.ToUserId=#ToUserId
Order By N.Identifier DESC
END
my Custom View Model
public class NotificationModel
{
public string ToUserName { get; set; }
public string FromUserName { get; set; }
public NotificationClickType NotificationClickType { get; set; }
public int TemplateId { get; set; }
public string JobTitle { get; set; }
public int JobIdentifier { get; set; }
public int BidIdentifier { get; set; }
}
I have created same ViewModel. But every where I have seen Using SP i can return Only single table data which I have added in my DbContext class.
When you add your stored procedure to the EF Model, it will automatically create a complex type. (At least it did for me, using Model First).
The name of the complex type is the SP name with “_Result” added, e.g. GetUserNotification_Result.
You can use the complex type like:
using (Entities dbContext = new Entities())
{
List<GetUserNotification_Result > data = dbContext.GetUserNotification(userId).ToList();
}
Alternatively, https://msdn.microsoft.com/en-us/data/jj691402.aspx shows how to use Code first. How to call Stored Procedure in Entity Framework 6 (Code-First)? may also be helpful.
I want to display information from a user-defined function in ASP.NET MVC 4 Razor. The function in the database looks like this:
CREATE FUNCTION dbo.udf_GetReportsByController(#controller_account VARCHAR(128))
RETURNS #retReports table (
ID INTEGER IDENTITY PRIMARY KEY,
ProviderID VARCHAR(50) NOT NULL ,
VertragID INTEGER NOT NULL,
Leistungszeitraum_von DATE NOT NULL,
Leistungszeitraum_bis DATE NOT NULL,
ReportklasseID VARCHAR(50) NOT NULL,
Version INTEGER NOT NULL,
Status VARCHAR(64)
)
AS
BEGIN
//Do some stuff and insert in the ret-table
RETURN;
END;
GO
Entity Framework mapped this in this class:
public partial class udf_GetReportsByController_Result
{
[Column("ID")]
public int ID { get; set; }
[Column("ProviderID")]
public string ProviderID { get; set; }
[Column("VertragID")]
public int VertragID { get; set; }
[Column("Leistungszeitraum_von")]
public System.DateTime Leistungszeitraum_von { get; set; }
[Column("Leistungszeitraum_bis")]
public System.DateTime Leistungszeitraum_bis { get; set; }
[Column("ReportklasseID")]
public string ReportklasseID { get; set; }
[Column("Version")]
public int Version { get; set; }
[Column("Status")]
public string Status { get; set; }
}
When I want to do some DbSet-Methods in my Controller, I get an Error message, that my model class is mapped as complex Type. I read that a complex type has no primary key, but I defined one in my function, and I need to address an element by it´s ID. How can I specify the primary key in this ret-Table?
When you do something like
context.Set<udf_GetReportsByController_Result>()
Entity Framework will look for a table that is mapped against the result type. However, this table doesn't exist. The function does return a table, but this is a table variable, not a table that's part of the database schema. Only entity types are mapped against schema tables.
You can only get udf_GetReportsByController_Result objects by calling the imported function:
var results = context.udf_GetReportsByController(accountNo);