Is there a way to generalize petapoco select query - c#

I'm trying to use PetaPoco ORM to get data from MySQL table. Below is the sample code to get only one record:
public void ShowEmployerDetails()
{
using (var db = new PetaPoco.Database ("mysql_mydb")) {
try {
var employers = db.Query <Employers> ("SELECT * FROM Employer WHERE id = 123456789");
foreach(var e in employers) {
return e.EmployerName;
}
} catch (Exception ex) {
log.Error (ex.Message);
}
}
}
There is need to pass the entity type in db.Query function. For each DB table, we need to define entities. In this case, the entity type is <Employers> and code is as:
public class Employers
{
public int id { get; set; }
public string EmployerName { get; set; }
}
Is there any way that we can generalize the above select query also? For instance, I'm planning to get output using single query like:
PetaPocoQueryEntities <Employers>.GetDataFromEntities ("SELECT * FROM Employer WHERE id = 123456789", "EmployerName");
And the query could be something like below where entity type could be passed as generic type T and wonder if there is some way in providing column name and getting column value dynamically:
public static class PetaPocoQueryEntities <T>
{
public static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public static T GetDataFromEntity (string sqlQuery, string colName )
{
using (var db = new PetaPoco.Database ("mysql_mydb")) {
try {
var entityResults = db.Query<T> (sqlQuery);
foreach (var result in entityResults) {
log.Info(colName + " value is " + result.colName);
}
} catch (Exception ex) {
log.Error (ex.Message);
}
return colName;
}
}
}
Please help.

There is need to pass the entity type in db.Query function
Short answer - yes. PetaPoco uses this information to map the data from the query back to entity correctly
Is there any way that we can generalize the above select query also? For instance, I'm planning to get output using single query like:
Yep, you could rig this up. This integration test should get you started.

Related

C# Entity Framework Core write raw SQL without model

First of all, I have seen this post: Raw SQL Query without DbSet - Entity Framework Core but I feel like it does not answer my question.
I have an ASP.NET Core MVC project with Entity Framework Core (newest at this date).
What I need to do is:
public IActionResult MyView(string NameOfTable)
{
// send model that is same as NameOfTable
// or
// send something like List<string> that has properties and names of columns
return View();
}
What I want to do is call a function and send the name of a table in database. It will find the table in the database and return the properties of table (column names, types of columns[int/ varchar..]).
What I think will work: either write raw SQL to database.
What I think is good answer: some snippet of how to write raw SQL to query the database without knowing a model. Or some way of overcoming this problem.
Thanks for everything. Sorry if this is dumb question.
Sorry for bothering you all. I have found solution. Here it is:
public class AA
{
public string One { get; set; }
public string Two { get; set; }
}
private async Task pokus()
{
List<AA> groups = new List<AA>();
var conn = _db.Database.GetDbConnection();
try
{
await conn.OpenAsync();
using (var command = conn.CreateCommand())
{
string query = "SELECT * FROM TABLE";
command.CommandText = query;
DbDataReader reader = await command.ExecuteReaderAsync();
if (reader.HasRows)
{
while (await reader.ReadAsync())
{
try
{
var row = new AA { One = reader.GetString(1), Two = reader.GetString(2) };
groups.Add(row);
}
catch { }
}
}
else
{
Console.WriteLine("Dont have rows");
}
reader.Dispose();
}
}
finally
{
conn.Close();
}
foreach(AA s in groups)
{
Console.WriteLine(s.One);
Console.WriteLine(s.Two);
}
}
I hope it will help somebody.

SQLite Xamarin Forms Database Table to ListView

Hello, I'm currently trying to show a table in an Android base App. however, my question is regarding:
var db = new SQLiteAsyncConnection(Database.DatabasePath.DB_Path);
foreach ( var N in db.Table<Database.Inventory>() )
{
InventoryItems.Add(new Store_Listing { Id = N.Id,
Description = N.Description, Style = N.Style });
This part of the code that is taking an error of:
foreach statement cannor operate on variables of type 'AsyncTableQuery< ... > ' because 'AsyncTableQuery< ... >' does not contain a public instance for 'GetEnumerator'
foreach ( var N in db.Table<Database.Inventory>() ) "
I have looked into the Database.Inventory Class, and it is set to public class, and all the elements are exactly as the SQLite database (Type 3) table.
Honestly I don't really know what is wrong.
There are no issues connecting the database .db3, but these error is unknown to me since I'm quite new to the SQLite and Android.App Dev.
This is the code for the Database.Inventory:
using System;
using System.Collections.Generic;
using System.Text;
using SQLite;
namespace Inventory.Database
{
[Table("Inventory")]
public class Inventory
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Description { get; set; }
public string Style { get; set; }
}
}
Thank You for your feedback in advance.
You are calling a new instance of SQLiteAsyncConnection, which uses async instructions for all its methods (where needed). In the SQLite NuGet source code, you will find that Table<T> returns a variable of type AsyncTableQuery. AsyncTableQuery does not implement or inherit anything. It's a stand-alone class, which means it cannot access the method GetEnumerator that it is expecting to access for a foreach statement.
So, in your code where you have:
var db = new SQLiteAsyncConnection(Database.DatabasePath);
foreach ( var N in db.Table<Database.Inventory>() ) // Table<T> does *not* return a List<T>!!
{
InventoryItems.Add(new Store_Listing
{
Id = N.Id,
Description = N.Description,
Style = N.Style
});
}
You're trying to access what you think is a List, but is actually an AsyncTableQuery.
How to get around this
If you look at the AsyncTableQuery class, it has a ToListAsync method, which converts the AsyncTableQuery object to a List<T> object. Simply calling this will convert your Table<T> to a List<T> like so:
var db = new SQLiteAsyncConnection(Database.DatabasePath);
var myList = await db.Table<Database.Inventory>().ToListAsync();
// TODO: Add a null or empty check to prevent NullExceptions.
foreach(var N in myList)
{
// Do your stuff
}
var results = await db.Table<Database.Inventory>();
var query = conn.Table<Database.Inventory>();
query.ToListAsync().ContinueWith((t) =>
{
foreach (var N in t.Result)
{
// code from your foreach loop
}
});

SqlConnection.Open() returns a StackOverflow Exception

I tried to find a solution for this but couldn't. Here's the problem:
I'm loading data of a bunch of users and creating an object for each user. Each user object has many object properties. Here's the structure:
public class User {
public int ID { get; set; }
public string Name { get; set; }
public City City { get; set; }
public Office Office { get; set; }
}
The City class:
public class City {
public int ID { get; set; }
public string Name { get; set; }
public string Keyword { get; set; }
}
The Office class:
public class Office {
public int ID { get; set; }
public string Address { get; set; }
public int CityID { get; set; }
}
The user object has many other similar properties like City & Office which are basically class objects.
Now here's the main issue. Whenever I try to load all the users into a collection of dictionary, StackOverflow exception occurs at SqlCon.Open() (See the "Fetch" function I've written below). Here's how I'm loading everything:
//Code to load users
Dictionary<int, User> Users = new Dictionary<int, Users>();
DataTable usersData = new DataTable();
//The Fetch function has two version. The first one; which is mentioned in this post, returns the result as Dictionary<string, object>().
//The second version of the function returns the result in the form of the a DataTable and is only used when multiple rows are required from the database. The following returns a set of rows in a DataTable.
Globals.MainDatabase.Fetch("SELECT * FROM users", out usersData);
foreach (DataRow row in usersData.Rows) {
User user = new User();
user.ID = Convert.ToInt32(row["id"]);
user.Name = row["name"].ToString();
user.City = Cities.Get(Convert.ToInt32(row["city_id"]));
user.Office = Offices.Get(Convert.ToInt32(row["office_id"]));
Users.Add(user.ID, user);
}
The methods "Cities.Get(Int32 id)" and "Offices.Get(Int32 id)" uses the following function to fetch data from the database.
public void Fetch(string query, out Dictionary<string, object> results) {
var dict = new Dictionary<string, object>();
try {
using (SqlConnection SqlCon = new SqlConnection(ConnectionString)) {
using (SqlCmd = new SqlCommand()) {
SqlCmd.Connection = SqlCon;
SqlCmd.CommandType = CommandType.Text;
SqlCmd.CommandText = query;
SqlCon.Open();
DataTable temp = new DataTable();
using (SqlDataAdapter SqlAdp = new SqlDataAdapter(SqlCmd)) {
SqlAdp.SelectCommand = SqlCmd;
SqlAdp.Fill(temp);
}
DataRow row = temp.Rows[0];
temp = null;
dict = row.Table.Columns
.Cast<DataColumn>()
.ToDictionary(col => col.ColumnName, col => row.Field<object>(col.ColumnName));
}
}
}
catch (Exception ex) {
HandleException(ex, "An error occurred when tried to fetch data.", query);
}
results = dict;
dict = null;
}
I realize that this "Fetch" function is being called multiple times when creating the user object. The "StackOverflow" exception occurs exactly at this line:
SqlCon.Open();
How can I solve this error? or probably I should use a better approach to do this?
A bit too long for a comment
Do you really need to load ALL data from the database? It is better to just grab the columns and rows you need.
Why are you copying DataTables in to Dictionaries? What is wrong with just using a DataTable?
99.9% of the time you will have better performance performing JOINs in the database.
Don't try to roll your own 'ORM'. Use something like Dapper if you don't want the bloat of EF or NHibernate. Or, stick to ADO (DataTable, DataAdapter etc.)
using (SqlConnection SqlCon = new SqlConnection(ConnectionString)) {
Is your connection string variable really named ConnectionString? Is it possible you are having a name clash with the type? Since it is not declared in the shown code, I assume it is a class variable, so you should respect the conventional naming convention, which would be _connectionString. What does your connection string look like?
Alright folks, I figured it out. It was all because of faulty architecture of the whole demo application. Some objects have one or more other objects as properties and due to some silly faults in the architecture; the "fetch" operation which serves as the base of fetching data from the database, was recursively called resulting into StackOverflow exception which was actually a large number of database connections being initialized ultimately growing the heap size to an extent which causes the exception.
I tried to summarize everything in the paragraph written above because posting the complete source code is useless considering the large amount of code.
Thank you everyone who helped, especially #Guffa's comment on the main post which forced me to investigate the whole issue from scratch rather than sticking to the exception stack.

Insert information from view into another table without user input

I am trying to create a web service, with C# and Visual Studio 2010, that will insert values form a view table into another table using a SQL query. In the past we have used this type of service but we have asked the user to enter the values they want to update, but with the query I am using there is no need for user input since the values will be inserted only when one of the field values is NULL.
Below is the code I been trying to use, I adapted the code we used before, but in here I am still expecting user input. How can I update my code as to not expect the input. I know I can just run this query from MySQL but I want be able to create a button in our site so other people can do it. Here is the code that runs the query:
public void AddUsers(string json)
{
GetDbInstance();
var sql = "insert into dbo.ASPECTS_users_activity " +
"(user_id, usr_address, usr_phone, usr_email, usr_website, usr_photo ) " +
"select #v.customer_id, #usr_address, #usr_phone, #usr_email, #usr_website, #usr_photo " +
"from dbo.ASPECTS_current_users_vw v where usr_photo is NULL;";
var usrInformation = JsonConvert.DeserializeObject<UpdateExhibitors>(json);
Console.WriteLine(usrInformation);
var conn = db.Connection();
try
{
SqlCommand command = new SqlCommand(sql, conn);
command.Parameters.AddWithValue("#v.customer_id", usrInformation.master_customer_id);
command.Parameters.AddWithValue("#usr_address", "123 XYZ Post, Columbus, IN");
command.Parameters.AddWithValue("#usr_phone", "555-555-5555");
command.Parameters.AddWithValue("#usr_email", "test#sample.com");
command.Parameters.AddWithValue("#usr_website", "http://www.sample.com");
command.Parameters.AddWithValue("#usr_photo", "");
command.ExecuteNonQuery();
command.Dispose();
command = null;
}
catch (Exception e)
{
throw new Exception(e.ToString(), e);
}
finally
{
conn.Close();
}
}
Here is the Model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Customer_Insert.Models {
public class AddUsers {
public string customer_id { get; set; }
public string usr_address { get; set; }
public string usr_phone { get; set; }
public string usr_email { get; set; }
public string usr_website { get; set; }
public string usr_photo { get; set; }
}
}
Just so you know the only field that will be populated from the view to will be the customer_id field, all the other fields will have default values, which will be updated at another point. Not many people here know SQL so creating this option will provide an option if I am not around.
Since you "can just run this query from MySQL", I would save that query as a Stored Procedure in your database. Then you need neither SQL nor parameters in your code.
SqlCommand command = new SqlCommand("yourStoredProcedureName", conn);
command.ExecuteNonQuery();
...

Code-First Entity Framework w/ Stored Procedure returning results from complex Full-text Searches

I am looking for design advice for the following scenario:
I have a code-first EF5 MVC application. I am building a full-text search function which will incorporate multiple weighted columns from many tables. As I cannot create view with an index from these tables (some of them contain text / binary columns), I have created a stored procedure which will output the ID of my object (eg. PersonID) and the rank associated with that object based on the search terms.
My current approach is to create a helper class for executing full text searches which call the stored procedure(s) and load all the objects from the context based on the returned IDs.
My questions are:
Does my approach seem sensible / follow reasonable best practice?
Has anyone else done something similar with any lessons learned?
Is there a way to do this more efficiently (i.e. have the results of the stored procedure return/map to the entities directly without an additional look-up required?)
UPDATE
Moved my detailed implementation from an edit of the question into its own answer to be more in line with what is recommended frequently # meta.stackexchange.com
Seeing as you can't use SQL methods like containstable with entityframework code first which the rest of your application could be using you could be 'forced' to do something with a storedprocedure like your describe. Whether it's best practice I don't know. However it it gets the job done I don't see why it wouldn't be sensible.
Yes - I have and still am working on a project build around EF codefirst where I had to do a fairly complex search that included several search parameters marked as 'must have' and several values marked as 'nice to have' and in from that return a weighted result.
Depending on the complexity of the result set I don't think you need to do a second roundtrip to the database and I will show you a way I have been doing it below.
Bear in mind that below is simply an example:
public List<Person> GetPeople(params string[] p)
{
var people = new List<Person>();
using (var db = new DataContext())
{
var context = ((IObjectContextAdapter)db).ObjectContext;
db.Database.Connection.Open();
var command = db.Database.Connection.CreateCommand();
command.CommandText = "SomeStoredProcedureReturningWeightedResultSetOfPeople";
command.CommandType = System.Data.CommandType.StoredProcedure;
//Add parameters to command object
people = context.Translate<Person>(command.ExecuteReader()).ToList();
}
return people;
}
Even though the storedprocedure will have a column for the weight value it won't get mapped when you translate it.
You could potentially derive a class from Person that includes the weight value if you needed it.
Posting this as an answer rather than an edit to my question:
Taking some of the insight provided by #Drauka's (and google) here is what I did for my initial iteration.
Created the stored procedure to do the full text searching. It was really too complex to be done in EF even if supported (as one example some of my entities are related via business logic and I wanted to group them returning as a single result). The stored procedure maps to a DTO with the entity id's and a Rank.
I modified this blogger's snippet / code to make the call to the stored procedure, and populate my DTO: http://www.lucbos.net/2012/03/calling-stored-procedure-with-entity.html
I populate my results object with totals and paging information from the results of the stored procedure and then just load the entities for the current page of results:
int[] projectIDs = new int[Settings.Default.ResultsPerPage];
foreach (ProjectFTS_DTO dto in
RankedSearchResults
.Skip(Settings.Default.ResultsPerPage * (pageNum - 1))
.Take(Settings.Default.ResultsPerPage)) {
projectIDs[index] = dto.ProjectID;
index++;
}
IEnumerable<Project> projects = _repository.Projects
.Where(o=>projectIDs.Contains(o.ProjectID));
Full Implementation:
As this question receives a lot of views I thought it may be worth while to post more details of my final solution for others help or possible improvement.
The complete solution looks like:
DatabaseExtensions class:
public static class DatabaseExtensions {
public static IEnumerable<TResult> ExecuteStoredProcedure<TResult>(
this Database database,
IStoredProcedure<TResult> procedure,
string spName) {
var parameters = CreateSqlParametersFromProperties(procedure);
var format = CreateSPCommand<TResult>(parameters, spName);
return database.SqlQuery<TResult>(format, parameters.Cast<object>().ToArray());
}
private static List<SqlParameter> CreateSqlParametersFromProperties<TResult>
(IStoredProcedure<TResult> procedure) {
var procedureType = procedure.GetType();
var propertiesOfProcedure = procedureType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
var parameters =
propertiesOfProcedure.Select(propertyInfo => new SqlParameter(
string.Format("#{0}",
(object) propertyInfo.Name),
propertyInfo.GetValue(procedure, new object[] {})))
.ToList();
return parameters;
}
private static string CreateSPCommand<TResult>(List<SqlParameter> parameters, string spName)
{
var name = typeof(TResult).Name;
string queryString = string.Format("{0}", spName);
parameters.ForEach(x => queryString = string.Format("{0} {1},", queryString, x.ParameterName));
return queryString.TrimEnd(',');
}
public interface IStoredProcedure<TResult> {
}
}
Class to hold stored proc inputs:
class AdvancedFTS :
DatabaseExtensions.IStoredProcedure<AdvancedFTSDTO> {
public string SearchText { get; set; }
public int MinRank { get; set; }
public bool IncludeTitle { get; set; }
public bool IncludeDescription { get; set; }
public int StartYear { get; set; }
public int EndYear { get; set; }
public string FilterTags { get; set; }
}
Results object:
public class ResultsFTSDTO {
public int ID { get; set; }
public decimal weightRank { get; set; }
}
Finally calling the stored procedure:
public List<ResultsFTSDTO> getAdvancedFTSResults(
string searchText, int minRank,
bool IncludeTitle,
bool IncludeDescription,
int StartYear,
int EndYear,
string FilterTags) {
AdvancedFTS sp = new AdvancedFTS() {
SearchText = searchText,
MinRank = minRank,
IncludeTitle=IncludeTitle,
IncludeDescription=IncludeDescription,
StartYear=StartYear,
EndYear = EndYear,
FilterTags=FilterTags
};
IEnumerable<ResultsFTSDTO> resultSet = _context.Database.ExecuteStoredProcedure(sp, "ResultsAdvancedFTS");
return resultSet.ToList();
}

Categories