I have been playing with the EF in the last couple of days.
Our applications are based on SQL Anywhere 10 databases, and all our data access are done through stored procedures. Since EF is not supported by SA 10, I am testing EF with SA 11.
For this purpose i have create a small database with 2 tables and a couple of stored procedures (based on the nerddinner database from the asp.net mvc samples, see here )
I have created a model from the database, tables and stored procedures and did the necessary function imports.
I have a stored procedure with the following signature:
ALTER PROCEDURE "DBA"."get_dinner"( #dinner_id integer)
BEGIN
select dinner_id, title, event_date, description, hosted_by , contact_phone, address, country, latitude, longitude
from dba.dinners d
where d.dinner_id = #dinner_id
END
And the resulting function import code looks like this:
public global::System.Data.Objects.ObjectResult<dinner> get_dinner(global::System.Data.Objects.ObjectParameter dinner_id)
{
return base.ExecuteFunction<dinner>("get_dinner", dinner_id);
}
And that's the problem. Ideally, the code generated should accept an int parameter, instead of global::System.Data.Objects.ObjectParameter dinner_id
As far as I see, the edmx file has all the data it needs to interpret the parameter types correctly:
<Function Name="get_dinner" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="false" ParameterTypeSemantics="AllowImplicitConversion" Schema="DBA">
<Parameter Name="dinner_id" Type="int" Mode="InOut" />
</Function>
Am I missing something here? What else is needed in order to have a function import with the proper parameter type? Is this something you can correct by tweaking the edmx file, or is it a problem of the SA11 EF support implementation.
Hope someone can give me some further clues.
This is a known problem with InOut parameters and code gen for Function Imports.
We have been talking about making InOut parameters produce code like this:
public ObjectResults<dinner> get_dinner(ref int dinner_id);
Rather than what you have.
One thing to try is converting from an 'InOut' to an 'In' parameter. Code gen should then produce something like this:
public ObjectResults<dinner> get_dinner(int dinner_id);
The real question though is does it work if you call it?
Hope this background helps
Cheers
Alex
Related
I want to create 2 methods with the same name, same return type, but different parameters.
When I attempted this using the Model the following happened:
No error message was immediately generated when adding the second method to the model.
The second method did not appear in the GUI for the Model.
Both methods do appear in the XML file generated by the Model.
When I attempt to build the model I got the following error message:
Error 1 CF0075: Procedure
'_PR01_PayrollEmployeeFile_LoadBySocialSecurityNumber' for method
'LoadBySocialSecurityNumber(System.String socialSecurityNumber,
System.String companyCode)' with body 'LOAD(string
socialSecurityNumber, string companyCode) RAW' already exists. Try to
change method name or method
persistenceName. 0 0 Amikids.DataProWarehouse.Model
To the good support people at Softfluent: Give me 24 hours and check back to make sure the following solution fully works and I don't have any other issues.
I think I have the solution but have not fully tested and noticed something quirky in the XML after I did a build, but suspect I may have corrupted the XML file and don't have time to fully explore.
The solution (I think):
Set the persistenceName in the XML file. The persistenceName corresponds to the generated stored procedure name.
<cf:method name="TestMethod" body="LOAD(string x) ORDER BY FirstName" persistenceName="TestMethod1" />
<cf:method name="TestMethod" body="LOAD(string x, string y) ORDER BY LastName" persistenceName="TestMethod2" />
I have written a function in PostgreSQL for insertion as follows:
CREATE OR REPLACE FUNCTION public.insert_blog("Url" character)
RETURNS void AS
$BODY$Begin
Insert Into "Blogs"("Url") Values("Url");
End$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION public.insert_blog(character)
OWNER TO postgres;
The above function adds an entry into the Blogs table (Url is a parameter).
I am trying to use this function in .Net Core (Npgsql.EntityFrameworkCore.PostgreSQL) as follows:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(Blog blog)
{
if (ModelState.IsValid)
{
//This works fine
var count = _context.Blogs.FromSql("Select insert_blog({0})", blog.Url).Count();
//This doesn't work -- it gives an error of "42601: syntax error at or near "insert_blog""
//var count = _context.Blogs.FromSql("insert_blog #Url={0}", blog.Url).Count();
return RedirectToAction("Index");
}
return View(blog);
}
Can someone tell me why the second command is not working? Also, even if the first command is working, is it the right way?
Since I have to write .FromSql(...).Count() in order for it to work, if I remove .Count() item doesn't get inserted. Can someone tell me why this is happening?
Is there any good article on using .FromSql() or "Using Postgres functions in entity framework core" (I'd guess that this is a new feature and that that's why I couldn't find much data on this)?
Can some one tell me why the second command is not working? Also even if the first command is working, is it the right way?
It's simply just not the way PostgreSQL syntax works. Select insert_blog({0}) is indeed the right way.
Since I have to write .FromSql(...).Count() in order for it to work. If I remove ".Count()" item doesn't get inserted. Can someone tell me why this is happening?
FromSql behaves just like Where and other functions on an IQueryable. Execution is postponed until the results are requested, because it will try to do everything in one database query.
To make sure your query actually gets executed, you need to call a method that returns something other than IQueryable such as .Count() or .ToList(). More info can be found here: https://learn.microsoft.com/en-us/ef/core/querying/overview#when-queries-are-executed
EDIT: I have found a relevant answer already on stack overflow here:
XQuery [value()]: 'value()' requires a singleton (or empty sequence), found operand of type 'xdt:untypedAtomic *'
I have not dealt with XML in T-SQL before, and I am modifying an existing legacy stored proc, and picking most if it up through trial and error.
however I have hit a problem where trial and error is proving fruitless, and very slow. Think it's time to appeal to stack overflow gurus!
Here is some XML
<?xml version=\"1.0\"?>
<Notification xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">
<NotificationId>0</NotificationId>
<UserNotifications>
<UserNotification>
<UserNotificationId>0</UserNotificationId>
<NotificationId>0</NotificationId>
<UserId>13514</UserId>
<MessageTypeId>1</MessageTypeId>
</UserNotification>
<UserNotification>
<UserNotificationId>0</UserNotificationId>
<NotificationId>0</NotificationId>
<UserId>13514</UserId>
<MessageTypeId>2</MessageTypeId>
</UserNotification>
</UserNotifications>
</Notification>
The Stored Proc in question accepts the above XML as a parameter:
CREATE PROCEDURE [dbo].[Notification_Insert]
#ParametersXml XML
AS
BEGIN
The XML contains child "UserNotification" elements. I would like to select the UserId, MessageTypeId of each UserNotification, into a table like this
UserId | MessageTypeId
13514 | 1
13514 | 2
Obviously the size of the collection is not fixed.
My current attempt (which doesn't work - is along these lines:
DECLARE #UserDetails TABLE ( UserId INT, MessageTypeId INT);
INSERT INTO #UserDetails (UserId, MessageTypeId)
SELECT Tab.Col.value('#UserId','INT'),
Tab.Col.value('#MessageTypeId','INT')
FROM #ParametersXml.nodes('/Notification/UserNotifications[not(#xsi:nil = "true")][1]/UserNotification') AS Tab(Col)
But this never inserts anything..
I have been playing around with this for a while now and not had any joy :(
I would suggest going through the links below. I found them short and quick to go through:
http://blog.sqlauthority.com/2009/02/12/sql-server-simple-example-of-creating-xml-file-using-t-sql/
http://blog.sqlauthority.com/2009/02/13/sql-server-simple-example-of-reading-xml-file-using-t-sql/
I found the solution to this problem through further searching stack overflow.
The query I need (thanks to XQuery [value()]: 'value()' requires a singleton (or empty sequence), found operand of type 'xdt:untypedAtomic *')
INSERT INTO #UserDetails (UserId, MessageTypeId)
SELECT UserNotification.value('UserId[1]','INT'),
UserNotification.value('MessageTypeId[1]','INT')
FROM #ParametersXml.nodes('//Notification/UserNotifications') AS x(Coll)
cross apply #ParametersXml.nodes('//Notification/UserNotifications/UserNotification') as un(UserNotification)
I have a stored procedure in SQL Sever 2008 like:
ALTER PROCEDURE [dbo].[mySP]
#FirstName As varchar(30),
#LastName As varchar(30),
#ID As int
As
Begin
--......
End
then in EF imported this SP as a function like:
public ObjectResult<Nullable<global::System.Int32>> MyFunc(global::System.String LastName, global::System.String FirstName,Nullable<global::System.Int32> ID)
{
//....
}
it works fine for long time.
Then I add some new thing to EF edmx with "update from database" today, the function parameter changed! it became:
public ObjectResult<Nullable<global::System.Int32>> MyFunc(global::System.String LastName,Nullable<global::System.Int32> ID, global::System.String FirstName)
{
//....
}
It's hardto believe it. I already have many codes to call this func and it worked fine. Now all of them are not working. Even I can manually change the parameter, but maybe it back the orginal order with the generated-code!
How to resolve this problem.
This error occurs when using ALTER PROCEDURE on the stored Procedure, and does not appear in all cases.
We have been able to show that the parameter order is controlled by SQL and that after altering a stored proc parameter list (e.g. adding a parameter, especially one with a default value), then using 'Update Model From Database' on the Entity model, the parameter order becomes alphabetical. One possibility is that SQL has a mechanism for maintaining compatibility with compiled procs when an optional parameter is added, and this is manifesting as the observed behavior.
You need to fix it in the database. The only way we have been able to restore the correct parameter order is to DROP and CREATE the stored procedure, then update the model. No change to the storage model will survive the Update from database.
We are using SQL 2000.
As of EF4, default code generation is also based on a T4 template. By drilling into that T4 we can see how it generate codes for the function import:
foreach (EdmFunction edmFunction in container.FunctionImports)
{
IEnumerable<FunctionImportParameter> parameters =
FunctionImportParameter.Create(edmFunction.Parameters, code, ef);
string paramList = string.Join(", ", parameters.Select(p =>
p.FunctionParameterType + " " + p.FunctionParameterName).ToArray());
...
So it's exactly based on how your SSDL schema is look like under you model.
For example, for the uspUpdateEmployeePersonalInfo SP in Adventureworks database:
CREATE PROCEDURE [HumanResources].[uspUpdateEmployeeHireInfo]
#EmployeeID [int],
#Title [nvarchar](50),
#HireDate [datetime],
#RateChangeDate [datetime],
#Rate [money],
#PayFrequency [tinyint],
#CurrentFlag [dbo].[Flag]
The SSDL is like the below:
<Function Name="uspUpdateEmployeePersonalInfo" Aggregate="false"
BuiltIn="false" NiladicFunction="false" IsComposable="false"
ParameterTypeSemantics="AllowImplicitConversion" Schema="HumanResources">
<Parameter Name="EmployeeID" Type="int" Mode="In" />
<Parameter Name="NationalIDNumber" Type="nvarchar" Mode="In" />
<Parameter Name="BirthDate" Type="datetime" Mode="In" />
<Parameter Name="MaritalStatus" Type="nchar" Mode="In" />
<Parameter Name="Gender" Type="nchar" Mode="In" />
</Function>
And it will result in this code to be generated inside the ObjectContext:
public int UpdateEmployeePersonalInfo(Nullable<global::System.Int32> employeeID,
global::System.String nationalIDNumber,
Nullable<global::System.DateTime> birthDate,
global::System.String maritalStatus,
global::System.String gender)
My guess is that the order of the properties in your SSDL schema in your model has been changed and EF generates new codes to match that. So after validating that the parameters are declared in the desired order in you SP inside the database, try removing the SP from you model and update your model from database one more time and you'll see that the generated method code will be changed accordingly.
We are seeing this issue happen with lots of our stored procedure and we have not been able to identify why. Any new information on this will be a lot of help.
I have seen it happen on stored procedure with a lot of input and output parameters more consistently. Moreover on update form database command which when stored procedure is not changed and also edmx file is not changed but the designer causes the reorder of parameters.
Verify that the compatibility level of the database is not below 90
sp_dbcmptlevel 'your_database_name'
If it reports a value below 90 then run
sp_dbcmptlevel 'your_database_name', 90
Thanks to lajones in Codeplex
I am using code similar to this Support / KB article to return multiple recordsets to my C# program.
But I don't want C# code to be dependant on the physical sequence of the recordsets returned, in order to do it's job.
So my question is, "Is there a way to determine which set of records from a multiplerecordset resultset am I currently processing?"
I know I could probably decipher this indirectly by looking for a unique column name or something per resultset, but I think/hope there is a better way.
P.S. I am using Visual Studio 2008 Pro & SQL Server 2008 Express Edition.
No, because the SqlDataReader is forward only. As far as I know, the best you can do is open the reader with KeyInfo and inspect the schema data table created with the reader's GetSchemaTable method (or just inspect the fields, which is easier, but less reliable).
I spent a couple of days on this. I ended up just living with the physical order dependency. I heavily commented both the code method and the stored procedure with !!!IMPORTANT!!!, and included an #If...#End If to output the result sets when needed to validate the stored procedure output.
The following code snippet may help you.
Helpful Code
Dim fContainsNextResult As Boolean
Dim oReader As DbDataReader = Nothing
oReader = Me.SelectCommand.ExecuteReader(CommandBehavior.CloseConnection Or CommandBehavior.KeyInfo)
#If DEBUG_ignore Then
'load method of data table internally advances to the next result set
'therefore, must check to see if reader is closed instead of calling next result
Do
Dim oTable As New DataTable("Table")
oTable.Load(oReader)
oTable.WriteXml("C:\" + Environment.TickCount.ToString + ".xml")
oTable.Dispose()
Loop While oReader.IsClosed = False
'must re-open the connection
Me.SelectCommand.Connection.Open()
'reload data reader
oReader = Me.SelectCommand.ExecuteReader(CommandBehavior.CloseConnection Or CommandBehavior.KeyInfo)
#End If
Do
Dim oSchemaTable As DataTable = oReader.GetSchemaTable
'!!!IMPORTANT!!! PopulateTable expects the result sets in a specific order
' Therefore, if you suddenly start getting exceptions that only a novice would make
' the stored procedure has been changed!
PopulateTable(oReader, oDatabaseTable, _includeHiddenFields)
fContainsNextResult = oReader.NextResult
Loop While fContainsNextResult
Because you're explicitly stating in which order to execute the SQL statements the results will appear in that same order. In any case if you want to programmatically determine which recordset you're processing you still have to identify some columns in the result.