Im working with Entity Framework version 6.1.3 and I want to execute a SQL-query
which gathers information from multiple tables like this:
var result = context.Database.SqlQuery<SomeType>("SELECT SUM(d.PurchaseValue) AS 'Total', div.Name, l.Name " +
"FROM Device AS d " +
"RIGHT JOIN Location AS l " +
"ON d.LOCATION_ID = l.ID " +
"RIGHT JOIN Division AS div " +
"ON d.DIVISION_ID = div.ID " +
"GROUP BY div.Name, l.Name " +
"ORDER BY l.Name");
My question is, what should be the the type in SqlQuery<>? Or what is the proper way to execute a query like this and get a result out of it?
Here SomeType can be any type that has properties which match the names of the columns returned from the query.
For example, your query returns columns:
Total | Name
Therefore, your return type (class) can be similar to below...
public class SomeType
{
public string Name { get; set; }
public decimal Total { get; set; }
}
Your query will be
var result = context.Database.SqlQuery<List<SomeType>>(...);
Have you seen checked the Data Development Center for ways to do this?
Note 'Entity Framework allows you to query using LINQ with your entity classes. However, there may be times that you want to run queries using raw SQL directly against the database.'
So if you can, use LINQ. Check here and here to start.
I have this line of code as my query in C#:
cmd.CommandText = "SELECT *
FROM product
LEFT JOIN category ON product.category_id = category.id
WHERE product.id = #productId";
The product table has a column called name which I need.
This is the line I use in my application to retrieve it.
product.ProductName = reader.GetString(reader.GetOrdinal("\"product\".\"name\""));
The error I'm getting is
System.IndexOutOfRangeException: Field not found
on that line.
reader.GetOrdinal("name");
Firstly the resultset does not have a field named "product"."name", but rather one named "name". Consider that if you were to try to select from that resultset within PostgreSQL it would be the same case:
SELECT "product"."name" FROM
(SELECT *
FROM product
LEFT JOIN category ON product.category_id = category.id
WHERE product.id = #productId) subquery
Doesn't work, but:
SELECT "name" FROM
(SELECT *
FROM product
LEFT JOIN category ON product.category_id = category.id
WHERE product.id = #productId) subquery
Does.
Secondly, don't use the PostgreSQL escaping on the name of the field.
conn.Open();
String sql = "select CATEGORIES.CAT_NAME,PRODUCTS.PRO_MODEL,PRODUCTS.PRO_NAME,PRODUCTS.PRO_PRICE,PRODUCTS.PRO_IMAGE,PRODUCTS.PRO_DESCRIPTION,PRODUCTS.PRO_STATUS,PRODUCTS.PRO_ACTIVE" +
"from PRODUCTS INNER JOIN CATEGORIES on PRODUCTS.CAT_ID = CATEGORIES.CAT_ID";
My Query SQL runs fine in MYSQL Server but it has error in C# code Incorrect syntax near the keyword 'INNER'. hope have answer soon
You need to add space between (.PRO_ACTIVE" and "from) string concatenation.
.PRO_ACTIVE " + "from...
So your query should be:
String sql = "select CATEGORIES.CAT_NAME,PRODUCTS.PRO_MODEL,PRODUCTS.PRO_NAME,PRODUCTS.PRO_PRICE,PRODUCTS.PRO_IMAGE,PRODUCTS.PRO_DESCRIPTION,PRODUCTS.PRO_STATUS,PRODUCTS.PRO_ACTIVE"
+ " " +//explicit space
"from PRODUCTS INNER JOIN CATEGORIES on PRODUCTS.CAT_ID = CATEGORIES.CAT_ID";
You need a space at the end of the first line of your string -- C# doesn't put line feeds in.
change it to:
String sql = "select CATEGORIES.CAT_NAME,PRODUCTS.PRO_MODEL,PRODUCTS.PRO_NAME,PRODUCTS.PRO_PRICE,PRODUCTS.PRO_IMAGE,PRODUCTS.PRO_DESCRIPTION,PRODUCTS.PRO_STATUS,PRODUCTS.PRO_ACTIVE" +
" from PRODUCTS INNER JOIN CATEGORIES on PRODUCTS.CAT_ID = CATEGORIES.CAT_ID";
A space was missing between "..PRODUCTS.PRO_ACTIVE" and "from PRODUCTS.."
I have a products table and each product have a category field (this is not up to me to change and I need to read the data from it as it is), from the product table I wanted to update a category table with unique categories only (I also use a IFNULL to deal with empty categories), this is what I have so far that works:
string query = "INSERT INTO categories (name)
SELECT DISTINCT(IFNULL(category, \"Uncategorized\")) AS category_name
FROM products
WHERE NOT EXISTS (
SELECT name FROM categories
WHERE name = category_name)";
With the above query I am able to update the categories with unique category fields and every time I need to run it, it will add only categories that does not exist to the table.
But besides the above I also need to include a profile id with each category inserted something like this, which does not work:
string query = "INSERT INTO categories (profile_id, name)
SELECT " + profileId.ToString() + ",
DISTINCT(IFNULL(category, \"Uncategorized\")) AS category_name
FROM products
WHERE NOT EXISTS (
SELECT name FROM categories
WHERE name = category_name
AND profile_id = #profileId)";
Is there a way I could acomplish this with a query ?
The above gives me a message:
SQLite error near "DISTINCT": syntax error
SqliteMan error:
Query Error: near "DISTINCT": syntax error Unable to execute statement
Let me know if I havent been clear and what should I be more clear about.
You are using the DISTINCT keyword incorrectly: it is not a function. Try instead:
"INSERT INTO categories (profile_id, name)
SELECT DISTINCT '"+profileId.ToString()+"', IFNULL(category, 'Uncategorized')
FROM products
WHERE category_name NOT IN (
SELECT name FROM categories WHERE profile_id = "'+profileId.ToString()+"'
)
"
One assumes that profileId.ToString() is guaranteed to be safe from SQL injection, or else you would be escaping it/passing it as a parameter to a prepared statement?
I'm trying to use the multimapping feature of Dapper to return a list of ProductItems and associated Customers.
[Table("Product")]
public class ProductItem
{
public decimal ProductID { get; set; }
public string ProductName { get; set; }
public string AccountOpened { get; set; }
public Customer Customer { get; set; }
}
public class Customer
{
public decimal CustomerId { get; set; }
public string CustomerName { get; set; }
}
My Dapper code:
var sql = #"select * from Product p
inner join Customer c on p.CustomerId = c.CustomerId
order by p.ProductName";
var data = con.Query<ProductItem, Customer, ProductItem>(
sql,
(productItem, customer) => {
productItem.Customer = customer;
return productItem;
},
splitOn: "CustomerId,CustomerName"
);
This works fine, but I seem to have to add the complete column list to the "splitOn" parameter to return all the customers' properties. If I don't add "CustomerName", it returns null. Am I misunderstanding the core functionality of the multimapping feature? I don't want to have to add a complete list of column names each time.
I just ran a test that works fine:
var sql = "select cast(1 as decimal) ProductId, 'a' ProductName, 'x' AccountOpened, cast(1 as decimal) CustomerId, 'name' CustomerName";
var item = connection.Query<ProductItem, Customer, ProductItem>(sql,
(p, c) => { p.Customer = c; return p; }, splitOn: "CustomerId").First();
item.Customer.CustomerId.IsEqualTo(1);
The splitOn param needs to be specified as the split point, it defaults to Id. If there are multiple split points, you will need to add them in a comma delimited list.
Say your recordset looks like this:
ProductID | ProductName | AccountOpened | CustomerId | CustomerName
--------------------------------------- -------------------------
Dapper needs to know how to split the columns in this order into 2 objects. A cursory look shows that the Customer starts at the column CustomerId, hence splitOn: CustomerId.
There is a big caveat here, if the column ordering in the underlying table is flipped for some reason:
ProductID | ProductName | AccountOpened | CustomerName | CustomerId
--------------------------------------- -------------------------
splitOn: CustomerId will result in a null customer name.
If you specify CustomerId,CustomerName as split points, dapper assumes you are trying to split up the result set into 3 objects. First starts at the beginning, second starts at CustomerId, third at CustomerName.
Our tables are named similarly to yours, where something like "CustomerID" might be returned twice using a 'select *' operation. Therefore, Dapper is doing its job but just splitting too early (possibly), because the columns would be:
(select * might return):
ProductID,
ProductName,
CustomerID, --first CustomerID
AccountOpened,
CustomerID, --second CustomerID,
CustomerName.
This makes the splitOn: parameter not so useful, especially when you're not sure what order the columns are returned in. Of course you could manually specify columns... but it's 2017 and we just rarely do that anymore for basic object gets.
What we do, and it's worked great for thousands of queries for many many years, is simply use an alias for Id, and never specify splitOn (using Dapper's default 'Id').
select
p.*,
c.CustomerID AS Id,
c.*
...voila! Dapper will only split on Id by default, and that Id occurs before all the Customer columns. Of course it will add an extra column to your return resultset, but that is extremely minimal overhead for the added utility of knowing exactly which columns belong to what object. And you can easily expand this. Need address and country information?
select
p.*,
c.CustomerID AS Id,
c.*,
address.AddressID AS Id,
address.*,
country.CountryID AS Id,
country.*
Best of all, you're clearly showing in a minimal amount of SQL which columns are associated with which object. Dapper does the rest.
Assuming the following structure where '|' is the point of splitting and Ts are the entities to which the mapping should be applied.
TFirst TSecond TThird TFourth
------------------+-------------+-------------------+------------
col_1 col_2 col_3 | col_n col_m | col_A col_B col_C | col_9 col_8
------------------+-------------+-------------------+------------
Following is the Dapper query that you will have to write.
Query<TFirst, TSecond, TThird, TFourth, TResut> (
sql : query,
map: Func<TFirst, TSecond, TThird, TFourth, TResut> func,
parma: optional,
splitOn: "col_3, col_n, col_A, col_9")
So we want for TFirst to map to col_1 col_2 col_3, for TSecond to col_n col_m ...
The splitOn expression translates to:
Start mapping of all columns into TFirst till you find a column named or aliased as 'col_3', and also include 'col_3' into the mapping result.
Then start mapping into TSecond all columns starting from 'col_n' and continue mapping till new separator is found, which in this case is 'col_A', and mark the start of TThird mapping and so on.
The columns of the SQL query and the props of the mapping object are in a 1:1 relation (meaning that they should be named the same). If the column names resulting from the SQL query are different, you can alias them using the 'AS [Some_Alias_Name]' expression.
If you need to map a large entity write each field must be a hard task.
I tried #BlackjacketMack answer, but one of my tables has an Id Column other ones not (I know it's a DB design problem, but ...) then this insert an extra split on dapper, that's why
select
p.*,
c.CustomerID AS Id,
c.*,
address.AddressID AS Id,
address.*,
country.CountryID AS Id,
country.*
Doesn't work for me. Then I ended with a little change to this, just insert an split point with a name that doesn't match with any field on tables, In may case changed as Id by as _SplitPoint_, the final sql script looks like this:
select
p.*,
c.CustomerID AS _SplitPoint_,
c.*,
address.AddressID AS _SplitPoint_,
address.*,
country.CountryID AS _SplitPoint_,
country.*
Then in dapper add just one splitOn as this
cmd =
"SELECT Materials.*, " +
" Product.ItemtId as _SplitPoint_," +
" Product.*, " +
" MeasureUnit.IntIdUM as _SplitPoint_, " +
" MeasureUnit.* " +
"FROM Materials INNER JOIN " +
" Product ON Materials.ItemtId = Product.ItemtId INNER JOIN " +
" MeasureUnit ON Materials.IntIdUM = MeasureUnit.IntIdUM " +
List < Materials> fTecnica3 = (await dpCx.QueryAsync<Materials>(
cmd,
new[] { typeof(Materials), typeof(Product), typeof(MeasureUnit) },
(objects) =>
{
Materials mat = (Materials)objects[0];
mat.Product = (Product)objects[1];
mat.MeasureUnit = (MeasureUnit)objects[2];
return mat;
},
splitOn: "_SplitPoint_"
)).ToList();
There is one more caveat. If CustomerId field is null (typically in queries with left join) Dapper creates ProductItem with Customer = null. In the example above:
var sql = "select cast(1 as decimal) ProductId, 'a' ProductName, 'x' AccountOpened, cast(null as decimal) CustomerId, 'n' CustomerName";
var item = connection.Query<ProductItem, Customer, ProductItem>(sql, (p, c) => { p.Customer = c; return p; }, splitOn: "CustomerId").First();
Debug.Assert(item.Customer == null);
And even one more caveat/trap. If you don't map the field specified in splitOn and that field contains null Dapper creates and fills the related object (Customer in this case). To demonstrate use this class with previous sql:
public class Customer
{
//public decimal CustomerId { get; set; }
public string CustomerName { get; set; }
}
...
Debug.Assert(item.Customer != null);
Debug.Assert(item.Customer.CustomerName == "n");
I do this generically in my repo, works good for my use case. I thought I'd share. Maybe someone will extend this further.
Some drawbacks are:
This assumes your foreign key properties are the name of your child object + "Id", e.g. UnitId.
I have it only mapping 1 child object to the parent.
The code:
public IEnumerable<TParent> GetParentChild<TParent, TChild>()
{
var sql = string.Format(#"select * from {0} p
inner join {1} c on p.{1}Id = c.Id",
typeof(TParent).Name, typeof(TChild).Name);
Debug.WriteLine(sql);
var data = _con.Query<TParent, TChild, TParent>(
sql,
(p, c) =>
{
p.GetType().GetProperty(typeof (TChild).Name).SetValue(p, c);
return p;
},
splitOn: typeof(TChild).Name + "Id");
return data;
}
I would like to note a very important aspect: the property name within the Entity must match the select statement. Another aspect of splitOn is how it looks for Id by default, so you don't have to specify it unless your naming is something like CustomerId, instead of Id. Let's look at these 2 approaches:
Approach 1
Entity Customer : Id Name
Your query should be something like:
SELECT c.Id as nameof{Customer.Id}, c.Foo As nameof{Customer.Name}.
Then your mapping understands the relationship between the Entity and the table.
Approach 2
Entity Customer: CustomerId, FancyName
Select c.Id as nameof{Customer.CustomerId}, c.WeirdAssName As nameof{Customer.FancyName}
and at the end of the mapping, you have to specify that the Id is the CustomerId by using the SplitOn.
I had an issue where I was not getting my values even though the mapping was correct technically because of a mismatch with the SQL statement.