I know this topic has been covered at length all across the interwebs, however I just simply don't understand anything that I've read/found. So I'm coming here.
I have stored procedure that takes an initial argument that indicates what action needs to take place (e.g. Insert, Check, and Get). I do this just to put all functionality for a particular object in one file (probably not the best design but it's where I am). So when this stored procedure is called, the first argument is what needs to happen - what to insert data, then 'Insert' is the first parameter.
Where I'm running into problems is trying to "load" data from the database into an object. I have Dapper but I just simply don't understand how any of it actually works.
The stored procedure:
CREATE OR ALTER PROCEDURE sp_Customers
#Action varchar(max)
#customername = varchar(max)
AS
BEGIN
SET NOCOUNT ON;
IF #Action = 'Retrieve'
BEGIN
SELECT customer_id, internal_id, customer_address, city, customer_state, zip, project, sponsor, customer_name
FROM tbl_Customers
WHERE customer_name = #customername;
RETURN
END
Customer class:
public class Customer{
public int customer_id {get;}
public int internal_id { get; set; }
public string customer_address { get; set; }
public string city { get; set; }
public string customer_state { get; set; }
public int zip { get; set; }
public int project { get; set; }
public int sponsor { get; set; }
public string customer_name { get; set; }
I simply want a function in this class the populates itself with information from the database. I have Dapper, but that confuses the crap out of me, I've read all sorts of things on the internets and that confuses the living crap out of me...is it really this difficult to load an object?
Something like this (split into multiple lines for readability, not tested or compiled)
public Customer GetCustomer(string name)
{
using (var connection = new SqlConnection(...))
{
var spName = "sp_Customers";
var parameters = new
{
Action = "Retrieve",
CustomerName = name
};
var commandType = CommandType.StoredProcedure;
return connection.QuerySingleOrDefault<Customer>(spName, parameters, commandType: commandType);
}
}
I have stored procedure that takes an initial argument that indicates
what action needs to take place (e.g. Insert, Check, and Get). I do
this just to put all functionality for a particular object in one file
(probably not the best design but it's where I am). So when this
stored procedure is called, the first argument is what needs to happen
- what to insert data, then 'Insert' is the first parameter.
I think that is where all of your Problems come from. Unless I am missing something, you should be unable to even return data from that stored procedure. Because in SQL Stored procedures of course need a defined return type. So that select can never send anything out.
You should have Stored Procedures with a single part of the CRUD operations:
Create should return the type of the PrimaryKey. Because you supriringly often need to extract the PrimaryKey value via the OUTPUT Syntax and return it to the programmin
Update should ahve a return type that informs of the result: Not found, not updated due to Update Race Condition Prevention, Updated
Select of course needs to return soemthing closer to the table
Related
First of all, I'm using Xamarin/C# with sqlite-net-pcl.
I create a table using SQLite.SQLiteConnection.CreateTable. T is one of my classes I can pass in, Here's an example:
public class Foo : IDatabaseItem
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Subject { get; set; }
public string Description { get; set; }
}
}
After inserting a record, I want to return the last key inserted. SQLiteConnection.Insert unfortunately returns the amount of rows inserted instead of the row inserted.
public int GetLastInsertedId()
{
return conn.Query<int>($"SELECT Max(Id) FROM {typeof(T).Name}").First();
}
The problem is the function above, it always returns 0. I've tried removing the .First to analyse the entire object it returns and it'll return an IEnumerable which correctly has the amount of rows in the DB within, but all of the results are 0.
Interestingly if I modify my function to return the original class it was created from and remove the "Max(Id", it works.
public Foo GetLastInsertedId()
{
return conn.Query<Foo>($"SELECT * FROM {typeof(T).Name}").First();
}
I believe I'm not referencing the column name correctly, I'd love to view the table to see what I'm working with but unfortunately the version of android I'm using doesn't appear to let you view an SQLite database (from Visual Studio).
I get that I can use Linq queries to quickly get at the data I want, but it feels like returning an entire object from the database is a bit bulky when all I want is the Id, especially as the database grows.
Any help would be appreciated, I'm sure I'm doing something daft...just can't suss out what.
I'm performing some maintenance work on a legacy system which can be a bit cumbersome to work with, and I'm running into an issue that, probably due to my lack of Oracle skills I can't seem to figure out what I'm doing wrong.
I need to add four new columns in a given table which, even though the system does use EF for database manipulation, it wasn't done with the much preferred code-first approach when it was first idealized. The entity is huge, so here's just a little snippet of the class itself before my changes:
[Table("SERVICO", Schema = "SCI")]
public class Servico
{
[DisplayName(#"Data e Hora da Solicitação")]
[Column("DT_HORA_SOLTCAO", Order = 0), Key]
public virtual DateTime DtHoraSoltcao { get; set; }
[DisplayName(#"Tipo de Colaborador")]
[StringLength(1)]
[Column("TP_COLABORADOR", Order = 1), Key]
public virtual string TpColaborador { get; set; }
[DisplayName(#"Número de Matricula do Colaborador")]
[Column("NR_MATRICULA_COLABORADOR", Order = 2), Key]
public virtual int NrMatriculaColaborador { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("ID_SERVICO")]
public virtual long IdServico { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("ID")]
public virtual long Id { get; set; }
[DisplayName(#"Serviço Original")]
[Column("CD_SERVICO_ORIGINAL")]
public virtual short? CdServicoOriginal { get; set; }
[DisplayName(#"Descrição do protocolo")]
[Column("DS_PROTOCOLO")]
public virtual string DsProtocolo { get; set; }
//And so on
}
In this architecture, every entity has a Business Object that mostly relies on Entity Framework to do its thing. Fetching a single entry from the database in the entity above couldn't be simplier:
public Servico GetServicoByIdServico(long idServico)
{
return _context.Servicos.FirstOrDefault(c => c.IdServico == idServico);
}
Suffice to say that this has been working for over a decade. But here's the catch: I added the four forementioned fields in the Servico class:
[DisplayName(#"Diâmetro instalação esgoto")]
[Column("DM_INSTAL_ESGOTO")]
public virtual short? DmInstalEsgoto { get; set; }
[DisplayName(#"Diâmetro coletor esgoto")]
[Column("DM_COLETOR_ESGOTO")]
public virtual short? DmColetorEsgoto { get; set; }
[DisplayName(#"Ponto de Interligação")]
[Column("ID_PONTO_INTERLIGACAO")]
public virtual short? IdPontoInterligacao { get; set; }
[DisplayName(#"Tipo de Material de Instalação Esgoto")]
[Column("TP_MATERIAL_INSTAL_ESGOTO")]
public virtual short? TpMaterialInstalEsgoto { get; set; }
I used the following script in order to generate their corresponding columns in the database:
BEGIN
BEGIN
EXECUTE IMMEDIATE '-- Add/modify columns
alter table SERVICO add DM_INSTAL_ESGOTO Number(3,0)';
EXCEPTION WHEN OTHERS THEN
IF SQLCODE != -1430 THEN RAISE; END IF;
END;
BEGIN
EXECUTE IMMEDIATE '-- Add/modify columns
alter table SERVICO add DM_COLETOR_ESGOTO Number(3,0)';
EXCEPTION WHEN OTHERS THEN
IF SQLCODE != -1430 THEN RAISE; END IF;
END;
BEGIN
EXECUTE IMMEDIATE '-- Add/modify columns
alter table SERVICO add ID_PONTO_INTERLIGACAO Number(3,0)';
EXCEPTION WHEN OTHERS THEN
IF SQLCODE != -1430 THEN RAISE; END IF;
END;
BEGIN
EXECUTE IMMEDIATE '-- Add/modify columns
alter table SERVICO add TP_MATERIAL_INSTAL_ESGOTO Number(3,0)';
EXCEPTION WHEN OTHERS THEN
IF SQLCODE != -1430 THEN RAISE; END IF;
END;
END;
And then, what was supposed to be quick and easy is turning out to be a nightmare. After these changes, whenever I run the GetServicoByIdServico method, a System.Data.Entity.Core.EntityCommandExecutionException is thrown:
OracleException: ORA-00904: "Extent1"."TP_MATERIAL_INSTAL_ESGOTO": invalid identifier
I double checked, triple checked those names, and removed one by one, testing it again every time I deleted one. But as long as one of those new fields are there (I didn't bother removing them from the database, I just deleted the properties from the Servico class), the last field will always raise the same exception. So I dug a little deeper. I decided to check the query EF was generating:
public Servico GetServicoByIdServico(long idServico)
{
IQueryable query = from x in _context.Servicos
where x.IdServico == idServico
select x;
//...
}
It is a long query, but here's the interesting part:
SELECT
"Extent1"."DT_HORA_SOLTCAO" AS "DT_HORA_SOLTCAO",
-- whatever
"Extent1"."DM_INSTAL_ESGOTO" AS "DM_INSTAL_ESGOTO", -- new column
"Extent1"."DM_COLETOR_ESGOTO" AS "DM_COLETOR_ESGOTO", -- new column
"Extent1"."ID_PONTO_INTERLIGACAO" AS "ID_PONTO_INTERLIGACAO", -- new column
"Extent1"."TP_MATERIAL_INSTAL_ESGOTO" AS "TP_MATERIAL_INSTAL_ESGOTO"-- new column
FROM "SCI"."SERVICO" "Extent1"
WHERE ("Extent1"."ID_SERVICO" = 1234567) -- let's say that's the ID I'm using throughout the whole thing
I pasted this query in my PL/SQL Developer and as it turns out, it works! So I decided in favor of a more direct approach the hopes that I was missing a setting somewhere in the C# side of things:
public Servico GetServicoByIdServico(long idServico)
{
var query = $#"SELECT
""Extent1"".""DT_HORA_SOLTCAO"" AS ""DT_HORA_SOLTCAO"",
//Ommited for brevity's sake
""Extent1"".""DM_INSTAL_ESGOTO"" AS ""DM_INSTAL_ESGOTO"",
""Extent1"".""DM_COLETOR_ESGOTO"" AS ""DM_COLETOR_ESGOTO"",
""Extent1"".""ID_PONTO_INTERLIGACAO"" AS ""ID_PONTO_INTERLIGACAO"",
""Extent1"".""TP_MATERIAL_INSTAL_ESGOTO"" AS ""TP_MATERIAL_INSTAL_ESGOTO""
FROM ""SCI"".""SERVICO"" ""Extent1""
WHERE (""Extent1"".""ID_SERVICO"" = :param1)";
OracleParameter p = new OracleParameter("param1", 1234567);
object[] parameters = new object[] { p };
var result = _context.Database.SqlQuery<object>(query, parameters).FirstOrDefault();
//...
}
But as long as one of those four new fields are there, an ORA-00904 will be raised. I used a plain object instead of Servico as the type parameter for SqlQuery to purposedly bypass that class in this test. Should I remove DM_INSTAL_ESGOTO, DM_COLETOR_ESGOTO, ID_PONTO_INTERLIGACAO and TP_MATERIAL_INSTAL_ESGOTO from this hard-coded query, the method will return correctly (even though I can't see the data within it, but that's not the point here).
Any help will be much appreciated.
I have an entity like this
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public int Id { get; set; }
}
And have a stored procedure as
CREATE PROCEDURE [dbo].[GetPersons]
AS
SELECT Id,FirstName FROM [dbo].[Persons]
When I call this stored procedure in My DbContext
var dataResult1 = dbContext.SqlQuery<Person>("[dbo].[GetPersons]");
The data reader is incompatible with the specified '..'. A member of the type, 'LastName', does not have a corresponding column in the data reader with the same name
I know if I define a new entity that has Id, FirstName and map stored procedure result to it everything is worke.
Now is there any way that I map my stored procedure result to Person Entity without define a new entity?
You could have you query look like this:
CREATE PROCEDURE [dbo].[GetPersons]
AS
SELECT Id, FirstName, '' as LastName, 0 as Age FROM [dbo].[Persons]
You aren't pulling them from the DB although they do still go across the network.
However you are now using Person to represent two different things, and this is almost always a bad idea. I think you are better off with two separate objects and maybe create and interface on Id and FirstName if you have code that needs to work with both.
I also wonder what you are doing that pulling the two extra columns has been identified as being a performance bottleneck, what is the difference between pulling and not pulling the columns? Or is it a premature optimization?
You have options (though I don't understand the purpose):
You could simply create a new Entity class that would only map those
2 columns.
You could use dynamic as the type (then you would lose
intellisense on the result set at least).
Instead of an SP you could
create that as an inline table valued function:
CREATE FUNCTION [dbo].[GetPersons] ()
RETURNS TABLE AS RETURN
(
SELECT Id,FirstName FROM [dbo].[Persons]
);
Then your code could simply look like this:
var dataResult1 = dbContext.SqlQuery<Person>(#"Select Id, FirstName,
'' as LastName, 0 as Age
FROM [dbo].[GetPersons]()");
OTOH an SP like this is questionable in the first place.
I am trying to execute a stored procedure this way:
var filterValues= context.Database.SqlQuery<FilterProcedureDTO>(
"[dbo].[sp_GetFilterValues] #FieldID", new SqlParameter("FieldID", filterID))
.ToList();
the issue is the filter values that come up have diffrent column name with each call as the user changes the filter on the view,though all come up as objects with an int column and string column,it seems they are bound to the specified model ie FilterProcedureDTO..which looks like
public class FilterProcedureDTO
{
public FilterProcedureDTO() {
//empty constructor
}
public int production_lineID { get; set; }
public string production_line_desc { get; set; }
}
so if the call produces taskID and task_desc this wont work anymore.
Another thing is some IDs are int32 and some are int16 so the code is not executing perfectly because of periodic exceptions
How can I get the stored procedure to return generic objects,just recognising the datatypes and not variable names too?
The SQLQuery method always attempts the column-to-property matching based on property name.
So you need to change your stored procedure so that it always returns the same name and datatype. You should be able to do that using aliases and casting in the sql.
Reference:
https://stackoverflow.com/a/9987939/150342
I am using ORMLite from ServieStack to call one of the Soterd procedure that I have.
When I call the stored procedure natively in SSMS it returns 3 rows with all data correctly
However, when I call it from ORMLite, it basically returns List<> of 3 objecs however, everyproperty in that object is pretty much having default value telling me that mapping failed somehow.
I am using following code to call the store procedure:
public List<DomainUserDto> CallDemoStoredProcedure(string id)
{
List<DomainUserDto> results = Db.SqlList<DomainUserDto>("TestJoinSupportForORMLite", cmd =>
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.AddParam("UserId", id);
});
return results;
}
Also, I was trying a SQL expression that goes with ORMLite and basically behaves in the same manner that my DTO is cnot mapped properly:
My SQL expression is as below:
List<DomainUserDto> rows = Db.Select<DomainUserDto>(Db.From<DomainUser>().
Join<Order>().
Join<Address>());
return rows;
Jus tas my stored procedure, it returns 3 rows. However, all the three rows has empty data meaning each property has default values and nothing is mapped.
while DomainUserDto looks like following:
public class DomainUserDto
{
public int Id { get; set; }
public string StreetName { get; set; }
public string ZipCode { get; set; }
public string Details { get; set; }
}
Attached is the sample result from the stored Procedure:
Please note following things:
There is not a single table in the Database that I can directly map to DomainUserDto
When I use the same functionality to map the object to a Domainobject with a table in Database, it work perfectly fine. It only fails when there is not a table in database which I can map to the POCO
All the create scripts for all the tables involved and the stored procedure under concern are at following location: gist.github.com/anonymous/ef54cc68dc9dd57ca6ef.
thanks
From docs for using for calling Stored Procedures using SqlList, try instead calling your Stored Procedure with:
var results = db.SqlList<DomainUserDto>(
"EXEC TestJoinSupportForORMLite #UserId", new { UserId = id });
The issue is because you're returning ProUser's string Id but your DomainUserDto is trying to coerce it into an int which prints the error out to the Console:
ERROR: System.FormatException: Input string was not in a correct format.
at System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
at System.Number.ParseInt32(String s, NumberStyles style, NumberFormatInfo info)
at System.String.System.IConvertible.ToInt32(IFormatProvider provider)
at System.Convert.ToInt32(Object value)
It will work after changing it to a string as seen in this commit, e.g:
public class DomainUserDto
{
public string Id { get; set; }
public string StreetName { get; set; }
public string ZipCode { get; set; }
public string Details { get; set; }
}
var rows = db.SqlList<DomainUserDto>(
"EXEC TestJoinSupportForORMLite #Id", new { user.Id });
rows.PrintDump();
I'll look at adding an config option to OrmLite in future to throw an exception instead of logging for coercion errors so the exception is more visible.
When I use the same functionality to map the object to a Domainobject
with a table in Database, it work perfectly fine. It only fails when
there is not a table in database which I can map to the POCO
So, try creating a View corresponding to the columns you want and use that to map to the POCO.