I had a hard time to come up with a title for this :)
I've got a WCF service hooked up to a SQL-server database. I retrieve data from the DB using ADO.NET.
On some simple operations I just retrieve from the DB and then send back a json representation of the EntityObjects i just fetched which works fine. However now I'm doing a more complex fetch with a procedure. The data retrieval works fine but the procdure itself returns more columns than the actual EntityObject (Table) has. Take this for an example:
create table Person
{
Name,
BirthDate
}
// Retrieve Persons from DB with procedure that also calculates each persons actual age!
public List<EntityObject> GetPersons()
{
var personList = new List<EntityObject>();
var dataSet = dbContext.ExceuteProcedure("GET_PERSONS_WITH_AGE", parameters);
var dataTable = dataSet.Tables["result"];
foreach (DataRow row in dataTable.Rows)
{
personList.Add( new PersonEntity
{
Name = (String)row["Name"],
BirthDate = (DateTime)row["BirthDate"],
// Here i want the actual age calculation result, but since the DB-table Person does'nt have this column,
// I can't set it.
}
}
return personList;
}
Do I need to create a CustomPersonClass for this which has this extra variable? Or can I somehow force another column into Table Person in my ADO.NET object?
Please consider that I'm novice about ADO.NET, if you see other code faults in my example (regarding methods of retrival as well) please let me know.
You may consider a DTO here, a data transfer object, this addresses the considerations of the caller/client of getPersons and will be generated as JSON back to the client. Call it PersonDTO or PersonResponse. It lives in the same class as the getPersons() method and is a public class. Change the signature to of getPersons() to List<PersonDTO> getPersons(). I would also captialize GetPersons() as it is a public method.
public class PersonDTO
{
property string Name;
property string BirthDate;
property string Age;
}
// Retrieve Persons from DB with procedure that also calculates each persons actual age!
public List<PersonDTO> getPersons()
{
var personList = new List<PersonDTO>();
var dataSet = dbContext.ExceuteProcedure("GET_PERSONS_WITH_AGE", parameters);
var dataTable = dataSet.Tables["result"];
foreach (DataRow row in dataTable.Rows)
{
personList.Add( new PersonDTO
{
Name = (String)row["Name"],
BirthDate = (DateTime)row["BirthDate"],
// Here i want the actual age calculation result, but since the DB-table Person does'nt have this column,
// I can't set it.
}
}
return personList;
}
Related
I am still new to C# and I am struggling to find a solution to my problem. My SQL dapper query returns a table (based on my understanding though it is not really a table if it is IEnumerable unlike what I am use to working with ADO and recordsets) with three columns col1, col2, and col3 and has multiple rows. I need to loop through this query result for each row and test the values (ie, a foreach loop where I check row(0).field1=5, row(1).field1 = 5 for each row, etc) do what I need to do. This seems so basic but I all the dapper tutorials I see do not show examples for this and if they do they seem to utilize class objects rather than accessing the results directly (if thats even possible or do you have to map the results to a model?) My code is as follows:
String query = "exec dbo.storeProcedure #jsonData, #mainDocJSON, #supportingDocsJSON";
IEnumerable queryResult;
using (var connection = new SqlConnection(connectionString))
{
queryResult = connection.Query(query, new { jsonData = jsonData, mainDocJSON = mainDocJSON, supportingDocsJSON = supportingDocsJSON });
}
I also end up returning IEnumerable results from the controller this code resides in so I send it back to the user in JSON using the following.
return Ok(queryResult);
connection.Query return a IEnumerable, why dont we create a class to map the set from ? Dapper is a micro-ORM, but still... ORM.
For ex: Your table return 3 column Id, Name, CreatedDate.
// declare a class to map the result first
public class ResultHolderDto
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime CreatedDate { get; set; }
}
// query somewhere
// This will return IEnumerable<ResultHolderDto>, feel free to play around as normal
var queryResult = await connection.QueryAsync<ResultHolderDto>(query, new { jsonData = jsonData, mainDocJSON = mainDocJSON, supportingDocsJSON = supportingDocsJSON });
foreach(var item in queryResult)
{
var col1Value = queryResult.Id;
var col2Value = queryResult.Name;
var col3Value = queryResult.CreatedDate;
// Then do something with col1Value, col2Value, col3Value...
}
My native join query produces a new result that is a combination of database tables, so I created a dto for that resulting object (will be a list of records on a screen).
I believe I need to make it an entity, so JPA can recognize it, would that be the best way to do it?
Also, the entity needs an id, and I was hoping to let jpa generate it auto, but I'm getting "Invalid parameter: Unknown column name id. ERRORCODE=-4460, SQLSTATE=null"
My result set contains 4 of the same records instead of 4 different, and I think it has to do with my id field not set properly
Any help would be appreciated on the subject, thanks.
`public interface ErrorCodeRepo extends JpaRepository<Errors, ErrorsPK> {
#Query("SELECT e.transDate, e.category FROM Errors e")
List<QueuedErrors> findQueuedErrors();
}`
DTO class:
`
public class QueuedErrors {
private String transDate;
private String category;
public QueuedErrors(String transDate, String category) {
this.transDate = transDate;
this.category = category;
}
public String getTransDate() {
return transDate;
}
public void setTransDate(String transDate) {
this.transDate = transDate;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
}
`
When you create navite query which contains results from multiple tables (after joins) you don't have to create new entities.
Better way to solve this problem is to projection with interface or class DTO.
For example, if you want to combine results from Person and Address Entities, simply create interface:
public interface PersonView {
String getFirstName();
String getLastName();
String getStreet();
}
You can see combined fileds from Person (firstName, lastName) and Address (street).
You have to use it as query response, like this:
#Query(...)
List<PersonView> getPersonWithStreet(String state);
You can read more about it here:
https://www.baeldung.com/spring-data-jpa-projections
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections
I have a view model called TdetailsVM as below:
public class TdetailsVM
{
public Tournaments tourney { get; set; }
public List<Participants> currentlyjoined { get; set; }
}
Now in the controller I am passing an ID of the tournament to the task:
public async Task<IactionResult> Details(guid id)
{
var ThisTourney = _context.Tournaments.FirstOrDefaultAsync(m => m.TID == id);
This will return the value for a specific tournament into ThisTourney where I pass it later to the view model
I need something similar like:
var ThisParticipants = (result "has many rows" from a stored procedure called SP_GetParticipants that needs a parameter =id)
Then I can pass the values to the view model as below
TdetailsVM tvm = new TdetailsVM()
{
tourney = ThisTourney,
currentlyjoined = ThisParticipants
}
// then I can return the view
return view(tvm);
Passing the data for the first requirement is fine and it works but how can I pass the stored procedure ?
Many thanks in advance
If you are using Entity Framework Core, then for calling your stored procedure you can use this line of code.
List<Participants> participants= _context.Participants.FromSql("SP_GetParticipants #ID",id).ToList();
Note how I passed #ID value to the stored procedure in FromSql
method and how mapped the result to List<Participants>. Please have a look at raw sql for more information.
After this line of code you have you list of Participants, then you can fill your parent ViewModel.
var result = new TdetailsVM
{
Tourney = thisTourney,
Currentlyjoined = Participants
};
Note that it is recommended to use PascalCase for naming your public
properties in your class (ViewModel) and camelCase for your
private ones. Please take a look at General Naming Conventions
for more information.
In ASP.NET Web API, it allows you to write ODATA queries in the url string to specify which data you want to return from a method. However the part that I'm having a hard time grasping is that ODATA works to filter an IQueryable collection of C# objects, not the database table itself.
This is impractical because really you would want to filter at the database level, as it would be horrible to return all objects from the database, load them into a C# IQueryable list, and then have the ODATA filter that list.
Here is the code, which uses NHibernate and Castle Active Record for data access:
public IQueryable<Message> GetAll()
{
return from m in MessageData.FindAllQueryable()
select ConvertToView(m);
}
public static IQueryable<Message> FindAllQueryable()
{
var criteria = DetachedCriteria.For<Message>()
.CreateAlias("MessageRecipients", "mr")
.AddOrder(new Order("Id", false));
return ActiveRecordMediator<Message>.FindAll(criteria).AsQueryable();
}
The end result of this code would be it returning all messages from the database. How do I allow the ODATA to perform its filters upon the database itself? Otherwise this whole concept of ODATA is completely impractical for real world situations.
Keep in mind, you're dealing with an IQueryable, which is not a physical list of objects. It is a query which has the potential to be executed and result in a list of entities. These queries can also be chained together:
var query = customRepository.Where(x => x.CustomerName == "John"); //no results generated
var query2 = query.Where(x => x.Salary > 100000); // still no results generated
var results = query2.ToList(); // now results are generated
You should be able to just return an instance of Session.Query from NHibernate. The OData functionality will then chain the criteria additional based on the URL and then enumerate the results back out.
Here is how I ended up getting it working. Much thanks to Andreas for leading me to NHibernate.OData.
In my controller action I get the odata from the url and pass it into my data access function:
public IQueryable<Message> GetAll(int authUserId, int userId, DateTime? startDate, DateTime? endDate)
{
LogWriter.Write(String.Format("Getting all messages for user {0}", userId));
//get messages and convert to view.
return from m in MessageData.FindAll(userId, startDate, endDate, GetOData())
select new Message(m);
}
protected string GetOData()
{
var odata = this.Request.RequestUri.Query;
odata = odata.Substring(odata.IndexOf("$"), odata.Length - odata.IndexOf("$"));
odata = odata.Replace("%20", " ");
return odata;
}
Inside the data access method, we get the NHibernate session and call session.ODataQuery:
public static IQueryable<Message> FindAll(int userId, DateTime? startDate, DateTime? endDate, string odata)
{
ICriteria query = GetSession().ODataQuery<Message>(odata);
var detachedCriteria = new ConvertedDetachedCriteria(query)
.CreateAlias("MessageRecipients", "mr")
.Add(Restrictions.Or(
Restrictions.Eq("SenderUserId", userId),
Restrictions.Eq("mr.Key.RecipientId", userId)
));
return FindAllQueryable(detachedCriteria);
}
public static ISession GetSession()
{
var factory = ActiveRecordMediator.GetSessionFactoryHolder().GetSessionFactories()[0];
return factory.OpenSession();
}
public static IQueryable<T> FindAllQueryable(DetachedCriteria criteria)
{
return ActiveRecordMediator<T>.FindAll(criteria).AsQueryable();
}
Also, a simple ConvertedDetachedCriteria class is needed to convert from ICriteria to DetachedCriteria.
public class ConvertedDetachedCriteria : DetachedCriteria
{
public ConvertedDetachedCriteria(ICriteria criteria)
: base((CriteriaImpl)criteria, criteria)
{
var impl = (CriteriaImpl)criteria;
impl.Session = null;
}
}
Hopefully this will help someone else. I now can write odata queries against my asp.net web api methods and have them filter at the db level, which is much more useful than filtering iqueryable c# objects!!
Adding to #Justin answer, without ActiveRecord one could just return something like
public IQueryable<Message> Get()
{
ICriteria query = _unitOfWork.CurrentSession.ODataQuery<Message>(GetOData());
return query.Future<Location>().AsQueryable<Location>();
}
// Taken from #Justin's answer
protected string GetOData()
{
var odata = this.Request.RequestUri.Query;
odata = odata.Substring(odata.IndexOf("$"), odata.Length - odata.IndexOf("$"));
odata = odata.Replace("%20", " ");
return odata;
}
This should put you up for testing!
Question
I'm trying to use the Dynamic Linq Sample from Microsoft with BindingList<T> objects. But it looks like the Dynamic Linq will only work with IQueryable. What's the deal here, why doesn't BindingList<T> implement IQueryable. And is there a way around this?
Background Detail: I have many data sets that I need to dynamically filter at run time. Here is an example:
BindingList<MyObject> list = new BindingList<MyObject>();
MyObject selectedObj = list.FirstOrDefault(o => o.Name == "Master P")
// then later ...
MyObject selectedObj = list.FirstOrDefault(o => o.City == "Boston")
I am trying to make these queries dynamic, so the user can choose from all properties of MyObject to use in the query.
There is an Extension method on BindingList; AsQueryable(). So you can use
list.AsQueryable();
But if you want to search on all criteria could you create a search that uses an instance of MyObject as the search criteria and then generated a result set based on the Criteria in the object using standard link.
For example:
public List<MyObject> Search(MyObject SearchCriteria)
{
BindingList<MyObject> list = new BindingList<MyObject>();
list.Add(new MyObject("Test", "Boston"));
list.Add(new MyObject("Test2", "Atlanta"));
IEnumerable<MyObject> results = list.AsEnumerable();
if (!String.IsNullOrEmpty(SearchCriteria.Name))
results = results.Where(l => l.Name.Contains(SearchCriteria.Name));
if (!String.IsNullOrEmpty(SearchCriteria.City))
results = results.Where(l => l.City.Contains(SearchCriteria.City));
return results.ToList();
}
So in the following, Results1 will have 2 results and Results 2 will have only 1.
List<MyObject> results1 = Search(new MyObject("Test", ""));
List<MyObject> results2 = Search(new MyObject("Test", "Boston"));
I used a simple structure for MyObject as an example in this:
public class MyObject
{
public MyObject(string name, string city)
{
this.Name = name;
this.City = city;
}
public string Name { get; set; }
public string City { get; set; }
}