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.
Related
I have two classes which are the result of a call to an API:
{
public int ID {get;set;}
public string name {get;set;}
public address address {get;set;}
}
public class address
{
public class address1 {get;set;}
public class address2 {get;set;}
public class city {get;set;}
}
I also have this code which stores API call in customers:
var customers = GetCustomers(bearerToken);
and the DB insert:
connection.Execute("dbo.Insert_Customer #ID, #Name", customers);
The issue I am having (I'm new to C sharp and API), I'm trying to get the contents of customers on one level.
However, address has two - contents are Address.Address1, Address.Address2.
I can't write the classes in that way due to the API.
Is there a way I can re-write this.
I know I need another list but not sure where to begin.
Ultimately this data is to be stored in a DB hence needs to be all on one level.
Hope all the above makes sense.
Many thanks in advance.
If you saying that you want to have a single type with Id, Name, Address1, Address2 and city at the same level so that you can pass it all to the stored procedure in the database, then you could use Linq to create a dynamic object with the data for each of the two classes like this -> Linq select to new object
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
All of my DAL functions are using dbContext.Database.SqlQuery to map stored procedure results in business logic objects.
My application became more complicated and I'm looking for a modern, "up to date" way to handle the following situations. I know that I can achieve this using the low-level ADO.NET component like SqlDataReader and map the result manually, but I am sure there is the best way to do so using Entity Framework 6.
To the question: with this command dbContext.Database.SqlQuery<MyClass>, I can not handle:
The stored procedure that returns 2 result sets
Mapping the result set to a complex datatype
Example:
public class Order
{
public Customer customer { get; set; }
public Items[] items { get; set; }
}
Again, I know that I can map it manually or with AutoMapper, but I'm looking for an "up to date" approach based on Entity Framework 6.
Yes, there's a way using Translate.
Adapted from the official documentation:
var cmd = dbContext.Database.Connection.CreateCommand();
cmd.CommandText = "[dbo].[GetAllCustomersAndOrders]";
dbContext.Database.Connection.Open();
// Run the sproc
var reader = cmd.ExecuteReader();
var Customers= ((IObjectContextAdapter)dbContext)
.ObjectContext
.Translate<Customer>(reader, "Customers", MergeOption.AppendOnly);
reader.NextResult();
var Orders = ((IObjectContextAdapter)db)
.ObjectContext
.Translate<Order>(reader, "Orders", MergeOption.AppendOnly);
As far as the problem of mapping
few columns from the result to a 2nd level complex type? for example:
SELECT FirstName, LastName, OrderId FROM Orders I want to map it to:
public class Order { public Customer customer { get; set; } public int
OrderId { get; set; } }
The best would be to use a CustomerId inside your Order table, referencing a Customer table, instead of FirstName/LastName. It would be a good refactoring, normalizing the database. Otherwise you will not have a real mapping between your objects and your database, since your Order object will have a Customer property that doesn't exist in your database. In that case, you will have to create a class, e.g. NormalizedOrder
public class NormalizedOrder {
int OrderId { get; set; };
Customer OrderCustomer { get; set; };
}
And then, after the code above where you retrieve all Orders, do something like
var NormalizedOrders = Orders.Select new Order(){OrderId = e.OrderId, OrderCustomer = new Customer(){FirstName=>e.FirstName,LastName=>e.LastName}};
What I am trying to do is read a database, row by row, and use the data from each row to initialize an object of the type that data represents. In this case I am reading rows of the Device table and trying to create Device objects with that data. I saw this SO link:
and I tried this snippet:
using(var dc = new DataContext(connectionString))
{
List<Person> people = dc.ExecuteQuery(#"
SELECT Id, Name Address
FROM [People]
WHERE [Name] = {0}", name).ToList(); // some LINQ too
}
But it is telling me
The type arguments for this usage cannot be inferred from the usage
Is this in principal correct or should I be using the BondIO serializer/deserializer? as mentioned here
Also the order of the members in the object may not be the same as the order of the columns in the database, is this relevant?
Later that same day....
I now have a DBContext with all my database objects defined like this:
public class MyContext : DBContext
{
public dbSet<Device>{ get; set;}
etc...
}
And I now try to get object using this snippet:
using (var db = new MyContext(ConnectionString))
{
var res = db.Device.Find(ID);
}
However this gives an exception message
Could not load type 'System.Data.Entity.ModelConfiguration.Conventions.AttributeToColumnAnnotationConvention`2
I have checked the database and it should return 1 value based on the PrimaryKey ID that I am passing. Anybody have any hints what I'm still doing wrong.
You cannot, because ExecuteQuery is for executing statements, not for querying database. You should use SqlQuery instead
What you can do is, to create a new class with the properties you want to set in your query, means a simplified version of your query. In your case
public class Device
{
public int Id {get;set}
public string Name {get;set}
public string Address {get;set}
}
then use it as
var people = dc.ExecuteQuery<Device>(#"
SELECT Id, Name Address
FROM [People]
WHERE [Name] = {0}", name).ToList();
What is the best approach at the repository level to hydrate an object's sub entities. For example: I need a list of all people in the system. Each person can have many phone numbers. How would I go about filling all this data in my repository?
public class Person
{
public int? Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
public List<Phone> PhoneNumbers { get; set; }
}
public IEnumerable<Person> GetPeople()
{
var persons = new List<Person>();
const string sql = "SELECT FirstName, LastName, MiddleName FROM Person.Person";
while (reader.Read())
{
var p = PersonFactory.GetPerson(reader);
p.PhoneNumbers = this.GetPhoneByPersonId(p.Id);
persons.Add(p);
}
return persons;
}
public IEnumerable<Phone> GetPhoneByPersonId(int? id)
{
while (reader.Read())
{
numbers.Add(PersonFactory.GetPhone(reader));
}
}
My problem with this approach is that I'm executing a bunch of sql statements that hit the DB. That just doesn't seem like the best approach. I cannot use any frameworks and must use a DataReader.
What is a better approach I could take? Also this is a simple example. There will be many more lists with my Person object.
One approach to implementing the GetPeople method would be execute a single SQL statement containing multiple result sets. If using SQL Server with MARS enabled, this will come from the server as a response to a single query. Otherwise, you can execute two queries, one to retrieve the people and another to retrieve all phone numbers that would be associated with those people.
Your queries can look something like:
SELECT FirstName, LastName, MiddleName FROM Person.Person WHERE -- omitted
SELECT PhoneNumber,Name FROM Person.PhoneNumbers WHERE -- restrict to same set as above
Again, you can either send both queries in a single command if the server supports it, or issue two separate queries. Either way, that is only two queries, which is no big deal. Do make sure that you aren't returning too many results at once.
Once you have the two result sets in memory, you can join them by person ID and associate all people instances with their corresponding phone numbers. Overall, you are doing either one or two database queries.
The IDataReader provides the method NextResult to enumerate multiple result sets returned by the query.