I'm writing the following SQL query using the TableAdapter Query Configuration Wizard in Visual Studio.
SELECT COUNT(*) AS census
FROM Inventory INNER JOIN Taxonomy ON Inventory.GlobalID = Taxonomy.GlobalID
WHERE (Inventory.Institution = #institution) AND (Inventory.Year = #year) AND
(Inventory.Nending > 0)
I'm trying to add the following criteria to the WHERE clause:
(Taxonomy.Class = ISNULL(#class, Taxonomy.Class))
so that either
1) only rows that match the #class input parameter are returned or
2) all rows are returned regardless of their TaxonomyGlobal.Class value.
When I add this statement to the query my C# code that calls the query throws a System.ArgumentNullException error and states the #class value cannot be null.
Any help on how to add this criterion to the WHERE clause would be appreciated.
C# code:
namespace CollectionMetrics
{
class DatabaseQueries
{
QueryDataSetTableAdapters.InventoryTableAdapter queryAdapter =
new QueryDataSetTableAdapters.InventoryTableAdapter();
public void CensusQuery(string institution, short year, string xclass)
{
int census = 0;
string localClass = xclass;
if (xclass == "All Classes") localClass = null;
census = (int)queryAdapter.CensusBySpecies(localClass, institution, year);
censusOutput.Add(census);
}
}
}
SQL:
(#class IS NULL OR Taxonomy.Class = #class)
Since you are using TableAdapter, you will need to edit the field to allow nulls:
https://msdn.microsoft.com/en-us/library/ms233762.aspx
Setting the AllowDbNull Property
To enable a query to accept null values In the Dataset Designer,
select the TableAdapter query that needs to accept null parameter
values. Select Parameters in the Properties window and click the
ellipsis (…) button to open the Parameters Collection Editor. Select
the parameter that allows null values and set the AllowDbNull property
to true.
If you are using SqlParameters:
C#
var param = new SqlParameter("#class", (object) classVariable ?? DBNull.Value);
Replace classVariable with the name of the variable you are using in your code to set the value for the #class SqlParameter. The cast to object is required because the variable does not have the same type as DBNull.
I once tried doing what you're trying to do, thinking this was a nifty way to ignore parameters that weren't passed from the front end (and therefore were NULL).
But then I learned that using ISNULL() in the WHERE clause like this prevents indexes from being used, making your query much SLOWER than if you used:
WHERE (Taxonomy.Class = #Class OR #Class IS NULL)
Unintuitive, I admit; the way you're trying looks like it would be cleaner and therefore faster, but for SQL performance, the most important thing is using available indexes, and so it turns out the A OR B approach is actually faster than the ISNULL() approach you want to use.
As to why you're getting an error, it's got to be something the wizard is enforcing. If you tried your query purely in SQL (using SSMS), it would allow it. UNLESS your query is actually in a stored procedure and #Class is a required parameter.
Related
I am looking for a query validator in C#, which allows me to parse the SQL text from a textbox and verify whether it's correct or not before sending it for execution (MS SQL or DB2 queries).
If you want to validate SQL syntax without the use of a database, the TSql100Parser class will do well for this situation.
Disclaimer, code borrowed from this post here Code to validate SQL Scripts
Pretty straightforward to use though. If it returns null, then there were no errors in parsing it.
using Microsoft.Data.Schema.ScriptDom;
using Microsoft.Data.Schema.ScriptDom.Sql;
public class SqlParser
{
public List<string> Parse(string sql)
{
TSql100Parser parser = new TSql100Parser(false);
IScriptFragment fragment;
IList<ParseError> errors;
fragment = parser.Parse(new StringReader(sql), out errors);
if (errors != null && errors.Count > 0)
{
List<string> errorList = new List<string>();
foreach (var error in errors)
{
errorList.Add(error.Message);
}
return errorList;
}
return null;
}
}
Set your query to sql with this hint:
set PARSEONLY on
It just checks your query and returns, like this:
set PARSEONLY on
select * from tablea
Returns no exception.
While
set PARSEONLY on
select * f rom tablea
returns
Msg 102, Level 15, State 1, Line 2
Incorrect syntax near 'f'.
If you would like to validate/parse just a SELECT statement, regardless of how "heavy-duty" that select statement is, I found out that the best and fastest way to validate a select statement is the following:
- in your code create 2 select statements (strings) such as:
1) Your valid select statement: SELECT * FROM HUGE_TABLE JOIN MULTIPLE_TABLES WHERE <<Condition>>
2) Create a similar select statement such as SELECT TOP 1 * FROM HUGE_TABLE JOIN MULTIPLE_TABLES WHERE <<Condition>>
- Parse/Validate just the second one, regardless of how many joins you have in there, it will parse it in milliseconds, such as:
SqlCommand sqlParse = new SqlCommand(ParseSelectStatement, sqlConn);
try
{
sqlConn.Open();
sqlParse.ExecuteNonQuery()
}
Hope it helps! Cheers!
I think this is what you are looking for. http://www.codeproject.com/KB/database/sqlvalidator.aspx
I have a SQL Server DB that my MVC app has access to via Entity Framework 6. I have a stored proc that does some totals and stores it in a temp table, then returns the results via a final select statement (simplified version):
select isnull(TotalDataShared, 0) as TotalDataShared from #main;
There are cases that TotalDataShared can be null, so I have to check for it. The auto-generated EF entity for the proc result looks like this (also simplified):
public partial class getUsageTotals_Result
{
public decimal TotalDataShared { get; set; }
}
My repo method calls the proc like this:
public UsageTotals GetCurrentUsage(int accountId)
{
var ret = _context.getUsageTotals(accountId).First();
return new UsageTotals {
DataShared = (double)ret.TotalDataShared
};
}
When TotalDataShared in #main is not null everything works fine. When It's null, I get the following error in my app:
The 'TotalDataShared' property on 'getUsageTotals_Result' could not be
set to a 'System.Double' value. You must set this property to a
non-null value of type 'System.Decimal'.
It seems to me that it's ignoring the ISNULL command. I've tried wrapping the last select in a table var since I've had issues in the past returning data from temp tables before in EF, but doesn't do any good.
I've also tried using COALESCE() and it gives the same results. I've also tried removing and recreating the object in EF, to no avail. I've also tried returning a non-zero float value if it's null (ISNULL(TotalDataShared, 4.441)) but that doesn't change anything either.
Not sure what to try next. Any ideas?
UPDATE:
Adding stored proc skeleton:
create proc getUsageTotals #accountid int
as
if object_id('tempdb..#main') is not null drop table #main;
create table #main (
TotalDataShared float,
)
. . .
. . .
select isnull(TotalDataShared, 4.441) as TotalDataShared from #main;
go
The final two lines of the proc are the select and "go", so the return value should be the select statement, unless I'm missing something.
Try this:
In your Entity Framework model (open the .edmx file), right-click on
the diagram background and select Model Browser.
Open the Complex Types tree.
Open your stored procedure, maybe called getUsageTotals_Result.
Right-click on the TotalDataShared line and select Properties.
Change Nullable to true.
I hope this is what you need.
As suggested in a comment, the procedure returns 0 rows if there are no results in the #main table. So _context.getUsageTotals(accountId) may return zero rows, and you can easily tackle that by:
var ret = _context.getUsageTotals(accountId)
.FirstOrDefault(); // FirstOrDefault!
return new UsageTotals {
DataShared = (double)(ret == null ? 0 : ret.TotalDataShared);
};
I have a method that will return the bare min results from a sproc to fill a select menu. When I want the bare min results I pass bool getMin = true to the sproc, and when I want the complete record I pass bool getMin = false.
This is causing the Entity FrameWork error of "The data reader is incompatible with the specified"
The most relevant portion of the error
{"Message":"An error has occurred.","ExceptionMessage":"The data reader is incompatible with the specified 'CatalogModel.proc_GetFramingSystems_Result'. A member of the type, 'FrameType', does not have a corresponding column in the data reader with the same name.","ExceptionType":"System.Data.EntityCommandExecutionException",
Obviously the error is telling me that when the data reader attempted to set the property 'FrameType' that is was not in the query results.
Now I understand the error, what I am wanting to know is that am I goning to have t split up this sql sproc into two sprocs or is there a work around for this?
My function below
public static IEnumerable<IFramingSystem> GetFramingSystems(int brandID, string frameType, string glazeMethod, bool getMin)
{
using (CatalogEntities db = new CatalogEntities())
{
return db.proc_GetFramingSystems(brandID, frameType, glazeMethod, getMin).ToList<IFramingSystem>();
};
}
My TSQL below
ALTER proc [Catelog].[proc_GetFramingSystems]
#BrandID INT,
#FrameType VARCHAR(26),
#GlazeMethod VARCHAR(7) ='Inside',
#getMin BIT = 0
as
BEGIN
SET NOCOUNT ON;
IF #getMin =0
BEGIN
SELECT c.ID,c.Name,c.Descr,c.FrameType,c.isSubFrame,
c.GlassThickness,c.GlassPosition,c.GlazingMethod,c.SillProfile
from Catelog.Component c
WHERE c.MyType ='Frame'
AND c.FrameType = #FrameType
AND c.GlazingMethod = #GlazeMethod
AND c.ID IN(
SELECT cp.ComponentID FROM Catelog.Part p JOIN
Catelog.ComponentPart cp ON p.ID = cp.PartID
WHERE p.BrandID = #BrandID
)
ORDER BY c.Name
END
ELSE
SELECT c.ID,c.Name,c.Descr
from Catelog.Component c
WHERE c.MyType ='Frame'
AND c.FrameType = #FrameType
AND c.GlazingMethod = #GlazeMethod
AND c.ID IN(
SELECT cp.ComponentID FROM Catelog.Part p JOIN
Catelog.ComponentPart cp ON p.ID = cp.PartID
WHERE p.BrandID = #BrandID
)
ORDER BY c.Name
SET NOCOUNT OFF;
END;
To me it seems that both branches of the IF return different data, the first branch returns 9 columns where the second - only three. I believe the EF can't reflect the IFramingSystem from the latter. Specifically, the column FrameType (and 5 other columns) are obviously missing:
...
SELECT c.ID,c.Name,c.Descr <- where are the remaining columns
from Catelog.Component c
...
I understand this is an old post; but, I wanted to share what I learned tonight about this. What I found that the 'most relevant portion of the error message is stating' is this.
db.proc_GetFramingSystems(brandID, frameType, glazeMethod, getMin).ToList<IFramingSystem>();
is expecting a column to be returned from the stored procedure with the alias of 'FrameType'.
I ran into this when I created a POCO (plain old clr object) class of my table with more programmer friendly names. Instead of a strongly typed name of say 'email_address', I wanted 'EmailAddy' and so forth. I created a mapped class stating this among other mapped columns.
this.Property(t => t.EmailAddy).HasColumnName("email_address");
Although this is necessary for other parts of EF to work, the mapping class is not referenced when executing a db.SqlQuery.
So, when the code below executes
var a = new SqlParameter("#fshipno", shipno);
return _context.db.SqlQuery<EmailList>("exec spGetEmailAddy #fshipno", a).ToList();
It generated the same error except instead of 'FrameType', it mentioned 'EmailAddy'.
The fix... I had to alias the 'email_address' column in my stored procedure to 'EmailAddy' to get it to map the returned dataset to my POCO.
EDIT: I have found that this only works if your method is returning an IEnumerable:
public IEnumerable<myPOCO> GetMyPoco()
You will get the same error message if you are attempting to return a single POCO object.
public myPOCO GetMyPoco()
If you are inserting/deleting/updating (these are considered by EF as 'non-query'), and can be called by our code using
MyDbContext.Database.ExecuteSqlCommand(insert into Table (Col1,Col2) values (1,2));
But if are doing select query for a raw SQL statement, then use
MyDbContext.DbSet<Table_name>.SqlQuery(select * from table_name).ToList();
or
MyDbContext.Database.SqlQuery(select * from table_name).ToList();
()
The SqlQuery() function, in EF, for strange reasons, throw exception Insert/delete/update operation. (The exception thrown is "A member of the type, does not have a corresponding column in the data reader with the same name.") But it has actually performed operation if you open your Sql Management Studio and check for the entries.
FYI http://www.entityframeworktutorial.net/EntityFramework4.3/raw-sql-query-in-entity-framework.aspx
I have to document several MS Access 2007 databases, each with hundreds of macros queries, etc and I'd like to automate the process in C#. For every .mdb file, myfirst goal is to extract schema information (tables, forms, macros and query names and definitions).
I'm using the following code using OleDB to get a list of all available tables by the user:
private static List<String> getTableNames(OleDbConnection db)
{
List<String> tableList = new List<String>();
DataTable schemaTable;
try
{
object[] objArrRestrict = new object[] { null, null, null, "TABLE" };
schemaTable = db.GetOleDbSchemaTable(OleDbSchemaGuid.Tables,objArrRestrict);
foreach(DataRow row in schemaTable.Rows) tableList.Add((String)row["TABLE_NAME"]);
}
catch(Exception e) {
Console.WriteLine(`"Table Name Querying Failed. Returning Empty List"`);
Console.WriteLine(e.Message);
}
return tableList;
}
For the columns I'm using a similar method simply feeding off the previous one like so:
object[] objArrRestrict = new object[] { null, null, tableName, null };
schemaCols = db.GetOleDbSchemaTable(OleDbSchemaGuid.Columns, objArrRestrict);
foreach (DataRow row in schemaCols.Rows) tableList.Add((String)row["COLUMN_NAME"]);
I'm not so sure how to get the macro information (name and definition), but after some research I decided to use the following to get the query information.
object[] objArrRestrict = new object[] { null, null, null, null };
schemaCols = db.GetOleDbSchemaTable(OleDbSchemaGuid.Procedures, objArrRestrict);
However when I run the last bit of code, I get the the very vaguely worded error message:
The SQL statement could not be executed because it contains ambiguous outer joins. To force one of the joins to be performed first, create a separate query in your SQL statement.
After some more research I modified the getTableNames method to grab queries through the "VIEWS" restriction, but that only got the stored SELECT queries in the data base and I also need the stored INSERT, UPDATE and DELETE queries.
Finally, I'm now using the following code to query the MsysObjects table directly.
String cmdString;
OleDbCommand queries = new OleDbCommand(cmdString,db);
OleDbDataReader reader = queries.ExecuteReader();
Using the following two cmdStrings interchangeably:
SELECT MSysObjects.Name FROM MsysObjects WHERE (Left$([Name],1)<>"~") AND
(MSysObjects.Type)=5 ORDER BY MSysObjects.Name
or
SELECT Name FROM MSysObjects WHERE (Name Not Like ""MSys*"") AND (Type In (1,4,6)) ORDER BY Name
Again, both strings also induce the same "Ambiguous Outer Join" error.
I've read up a little bit on this type of error and I know the general recommendation is to break apart the original query into multiple queries but as I'm using library functions to get this schema data, I'm not sure how exactly to go about doing that. I've aready spent days researching this and I'm at my wit's end. If someone could help me out with this issue or point me toward some way to sidestep this roadblock I would really appreciate it.
What happens if you group it like this?
SELECT
Name
FROM
MSysObjects
WHERE
Name Not Like 'MSys*'
AND
Type In (1,4,6)
ORDER BY Name
or this one
SELECT
Name
FROM
MSysObjects
WHERE
(
(Name Not Like ""MSys*"")
AND
(Type In (1,4,6))
)
ORDER BY Name
Is there a way I can determine in .NET, for any arbitrary SQL Server result set, if a given column in the result can contain nulls?
For example, if I have the statements
Select NullableColumn From MyTable
and
Select IsNull(NullableColumn, '5') as NotNullColumn From MyTable
and I get a datareader like this:
var cmd = new SqlCommand(statement, connection);
var rdr = cmd.ExecuteReader();
can I have a function like this?
bool ColumnMayHaveNullData(SqlDataReader rdr, int ordinal)
{
//????
}
I want it to return true for the first statement, and false for the second statement.
rdr.GetSchemaTable() doesn't work for this because it returns whether the underlying column can be null, which is not what I want. There are functions on datareader that return the underlying sql type of the field, but none seem to tell me if it can be null..
Unfortunately You can't because SQL server has no way of determining whether a field is nullable or not. You can do arbitral transformations on fields in result set (operators, function calls etc.) and those transformations do not have metadata about them whether they can or can't return null. So You have to figure that out manually or use views with schemabinding...
I'm a bit confused:
"doesn't work for this because it returns whether the underlying column can be null, which is not what I want. "
"but none seem to tell me if it can be null.."
You can query the underlying table to see if a column is null-able (assuming that is what you want).
SELECT IS_NULLABLE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'MyTable' AND COLUMN_NAME = 'MyColumn'