Select from table using XML column - sql-server

I am creating a task-scheduler on SQL Server 2008.
I have a table that I use to store tasks. Each task is a task name (e.g. ImportFile) and arguments. I store arguments in XML column, since different tasks have different signatures.
Table is as follows:
Id:integer(PK) | operation:nvarchar | Arguments:xml
Before queuing a task, I often need to verify that given task hasn't been scheduled yet. The lookup is done based on both operation and args.
Question: Using Linq-to-Sql how can I check if given operation+args is present in the queue already?
I am looking for something like:
var isTaskScheduled = db.Tasks.Any(t =>
t.Opearation == task.Operation &&
t.Arguments == task.ArgumentsAsXElement);
(which doesn't work because SQL Server can't compare XML type)
Any alternative implementation suggestions?

You might want to surface e.g. a string property that encapsultes your Arguments, or maybe it would be sufficient to have e.g. the length and a CRC of your Arguments as extra properties on your class:
public partial class Task
{
public int ArgumentLength
{ .... }
public int ArgumentCRC
{ .... }
}
That way, if you can compare length (of your XML) and the CRC and they match, you can be pretty sure and safe to assume the two XML's are identical. Your check would then be something like:
var isTaskScheduled =
db.Tasks.Any(t => t.Operation == task.Operation &&
t.ArgumentLength == task.ArgumentLength &&
t.ArgumentCRC == task.ArgumentCRC);
or something like that.

This may be a stretch, but you could use a "Hashcode" when saving the data to the database, then query on the hashcode value at a later date / time.
This assumes that you have a class that represents your task entity and that you have overridden the GetHashCode method of said class.
Now, when you go to query the database to see if the task is in the scheduled queue, you simply query on the hashcode, thus avoiding the need to do any xml poking at query time.
var t1 = new Task{Operation="Run", Arguments="someXElement.value"};
var t2 = new Task{Operation="Run", Arguments="someXElement.value"};
in the code above t1 == t2 because you are overriding GetHashCode and computing the hash for Operation+Arguments.Value. if you store the hashcode in the db, then you can easily tell if you have an object in the DB that equals the hash code that you are checking for.
This may be similar to what marc_s was talking about.

You can write a class which implements IComparable:
public class XMLArgument : IComparable
{
public XMLArgument(string argument)
{
}
public int CompareTo(object obj)
{
...
}
}
var isTaskScheduled = db.Tasks.Any(t =>
t.Opearation == task.Operation &&
(new XMLArgument(t.Arguments)).CompareTo(new XMLArgument(task.ArgumentsAsXElement)) == 0);

Related

Delete multiple objects with a single query (or in transaction)

I'm using Dapper with Dapper-Extensions. I'm currently deleting all the objects one-by-one:
dbConnection.Delete<MyObj>(data);
This is bad not only for performance, but also because if a delete fails I would like to rollback the entire operation. Is there a way to perform a "massive" delete, for example passing a list of objects instead of data?
You may pass IPredicate to delete multiple records based on condition (WHERE clause) in one go.
If you simply pass empty IPredicate, all records from the table will be deleted.
Following function handles both the cases:
protected void DeleteBy(IPredicate where)
{//If 'where' is null, this method will delete all rows from the table.
if(where == null)
where = new PredicateGroup { Operator = GroupOperator.And, Predicates = new List<IPredicate>() };//Send empty predicateGroup to delete all records.
var result = connection.Delete<TPoco>(predicate, ......);
}
In above code, TPoco is your POCO type which is mapped to database table you are talking about.
You can build the predicate something like below:
var predicateGroup = new PredicateGroup { Operator = GroupOperator.And, Predicates = new List<IPredicate>() };
if(!string.IsNullOrEmpty(filterValue))
predicateGroup.Predicates.Add(Predicates.Field<MyPoco>(x => x.MyProperty, Operator.Eq, PredicateGroup));
Transaction is different thing. You can put all your current code in transaction. You can put my code in transaction as well. With my code, transaction does not make much difference though; although it is recommended to always use transactions.
About passing list of objects, I do not see any way. Following are two extension methods of Dapper Extensions for deleting the record:
public static bool Delete<T>(this IDbConnection connection, object predicate, IDbTransaction transaction = null, int? commandTimeout = default(int?)) where T : class;
public static bool Delete<T>(this IDbConnection connection, T entity, IDbTransaction transaction = null, int? commandTimeout = default(int?)) where T : class;
None of it accepts list of objects. One accept predicate and other accepts single object.

Controlling NHIbernate search query output regarding parameters

When you use NHibernate to "fetch" a mapped object, it outputs a SELECT query to the database. It outputs this using parameters; so if I query a list of cars based on tenant ID and name, I get:
select Name, Location from Car where tenantID=#p0 and Name=#p1
This has the nice benefit of our database creating (and caching) a query plan based on this query and the result, so when it is run again, the query is much faster as it can load the plan from the cache.
The problem with this is that we are a multi-tenant database, and almost all of our indexes are partition aligned. Our tenants have vastly different data sets; one tenant could have 5 cars, while another could have 50,000. And so because NHibernate does this, it has the net effect of our database creating and caching a plan for the FIRST tenant that runs it. This plan is likely not efficient for subsequent tenants who run the query.
What I WANT to do is force NHibernate NOT to parameterize certain parameters; namely, the tenant ID. So I'd want the query to read:
select Name, Location from Car where tenantID=55 and Name=#p0
I can't figure out how to do this in the HBM.XML mapping. How can I dictate to NHibernate how to use parameters? Or can I just turn parameters off altogether?
OK everyone, I figured it out.
The way I did it was overriding the SqlClientDriver with my own custom driver that looks like this:
public class CustomSqlClientDriver : SqlClientDriver
{
private static Regex _partitionKeyReplacer = new Regex(#".PartitionKey=(#p0)", RegexOptions.Compiled);
public override void AdjustCommand(IDbCommand command)
{
var m = _tenantIDReplacer.Match(command.CommandText);
if (!m.Success)
return;
// replace the first parameter with the actual partition key
var parameterName = m.Groups[1].Value;
// find the parameter value
var tenantID = (IDbDataParameter ) command.Parameters[parameterName];
var valueOfTenantID = tenantID.Value;
// now replace the string
command.CommandText = _tenantIDReplacer.Replace(command.CommandText, ".TenantID=" + valueOfTenantID);
}
} }
I override the AdjustCommand method and use a Regex to replace the tenantID. This works; not sure if there's a better way, but I really didn't want to have to open up NHibernate and start messing with core code.
You'll have to register this custom driver in the connection.driver_class property of the SessionFactory upon initialization.
Hope this helps somebody!

EF stored procedure with dynamic number of columns in result set

At first I should note that I'm new in ASP.NET (however, I have some experience with C#) and Entity Framework. I work on a school project and there is quite complicated database containing energy consumption data. Those I need (I import, E export, reactive power C and L) are stored in one column as binary compressed so to get them out I have to use a stored procedure which calls some methods in custom assembly to decompress the column and restore the data.
The stored procedure has 4 arguments:
#identify int,
#startTime datetime,
#endTime datetime,
#args nvarchar(60) -- "Selector"
The selector is rather special, it's an argument where you specify what you want in the result set, e.g. 'i' for Import column only, 'i,e' for Import and Export. They designed it like this because it's faster if you need just one column rather then all columns (because of the way the compressed data are stored).
So, I've created an ADO.NET Entity Model called EnergyConsumptionDBModel, imported the stored procedure and created complex type EnergyConsumptionResult for the stored procedure return type as follows:
public partial class EnergyConsumptionResult
{
public System.DateTime Time { get; set; }
public double I { get; set; }
public double E { get; set; }
public double L { get; set; }
public double C { get; set; }
}
The column Time is always present in the result set, but the rest depends on the #args argument of the stored procedure. For example if I pick 'i,e,c,l' as an argument, it will return columns Time, I, E, C, L and everything is just fine but if I pick for example 'i' it returns Time, I which gives me an exception:
The data reader is incompatible with the specified 'EnergyConsumptionDBModel.EnergyConsumptionResult'.
A member of the type, 'E', does not have a corresponding column in the data reader with the same name.
So the question is, is there some simple way to solve this? Some kind of dynamic result mapping on complex type or is it much more simple to tell my colleague who designed the stored procedure to make it return all columns whatever is in #args but leave the un-used columns empty which is a solution my project leader may not like. Thanks for any help.
I don't think it is possible to automatically map resultsets with variable number of columns in EF. You specify the mapping at design time and EF relies on that. What you could do would be returning all columns but set the columns for which you don't return data to null. You may try using executing the stored procedure directly and use Translate method on ObjectContext (http://msdn.microsoft.com/en-us/library/dd466384.aspx) for materialization but I think this method will also expect columns in your reader that correspond to property names.

Entity Framework long running query

In my DB there is View "RqstLst"
I create EF Model from DB. Now I have entity RqstLst.
There is two variant of the same query
public void MyMethod()
{
context = new WaterMEntities();
var query = context.RqstLst;
dgRqstLst.ItemsSource = query; //dgRqstLst - DataGrid in WPF
}
and
public void MyMethod()
{
dgRqstLst.ItemsSource = this.GetRqstLst();
}
private IEnumerable<RqstLst> GetRqstLst()
{
context = new WaterMEntities();
string nativeSQLQuery = "SELECT * " +
"FROM dbo.RqstLst ";
ObjectResult<RqstLst> requestes =
context.ExecuteStoreQuery<RqstLst>(nativeSQLQuery);
return requestes;
}
execution time for first variant(LINQ to Entities) is 19 sec, for second, less then 1 sec.
I look it in sql server profiler. What i do wrong in first variant?
One big difference is that ExecuteStoreQuery doesn't attach the returned objects to the context (at least not the overload you are using) but your first query does (which costs time).
Try to define the same tracking behaviour in your first query like you have in the second query (= NoTracking):
context = new WaterMEntities();
context.RqstLst.MergeOption = MergeOption.NoTracking; // in System.Data.Objects
var query = context.RqstLst;
dgRqstLst.ItemsSource = query;
You didn't do anything wrong with the first option, but depending on your configuration, the query generated by the first one could be much more complex than your straight SQL execution. Have you used the profiler to see exactly what SQL the first query generates? For example, if RqstLst happens to be an abstract base class using TPT Inheritance, the generated SQL could be huge.

T-SQL IsNumeric() and Linq-to-SQL

I need to find the highest value from the database that satisfies a certain formatting convention. Specifically, I would like to find the highest value that looks like
EU999999 ('9' being any digit)
select max(col) will return something like 'EUZ...' for instance that I want to exclude.
The following query does the trick, but I can't produce this via Linq-to-SQL. There seems to be no translation for the isnumeric() function in SQL Server.
select max(col) from table where col like 'EU%'
and 1=isnumeric(replace(col, 'EU', ''))
Writing a database function, stored procedure, or anything else of that nature is far down the list of my preferred solutions, because this table is central to my app and I cannot easily replace the table object with something else.
What's the next-best solution?
Although ISNUMERIC is missing, you could always try the nearly equivalent NOT LIKE '%[^0-9]%, i.e., there is no non-digit in the string, or alternatively, the string is empty or consists only of digits:
from x in table
where SqlMethods.Like(x.col, 'EU[0-9]%') // starts with EU and at least one digit
&& !SqlMethods.Like(x.col, '__%[^0-9]%') // and no non-digits
select x;
Of course, if you know that the number of digits is fixed, this can be simplified to
from x in table
where SqlMethods.Like(x.col, 'EU[0-9][0-9][0-9][0-9][0-9][0-9]')
select x;
You could make use of the ISNUMERIC function by adding a method to a partial class for the DataContext. It would be similar to using a UDF.
In your DataContext's partial class add this:
partial class MyDataContext
{
[Function(Name = "ISNUMERIC", IsComposable = true)]
public int IsNumeric(string input)
{
throw new NotImplementedException(); // this won't get called
}
}
Then your code would use it in this manner:
var query = dc.TableName
.Select(p => new { p.Col, ReplacedText = p.Col.Replace("EU", "") })
.Where(p => SqlMethods.Like(p.Col, "EU%")
&& dc.IsNumeric(p.ReplacedText) == 1)
.OrderByDescending(p => p.ReplacedText)
.First()
.Col;
Console.WriteLine(query);
Or you could make use MAX:
var query = dc.TableName
.Select(p => new { p.Col, ReplacedText = p.Col.Replace("EU", "") })
.Where(p => SqlMethods.Like(p.Col, "EU%")
&& dc.IsNumeric(p.ReplacedText) == 1);
var result = query.Where(p => p.ReplacedText == query.Max(p => p.ReplacedText))
.First()
.Col;
Console.WriteLine("Max: {0}, Result: {1}", max, result);
Depending on your final goal it might be possible to stop at the max variable and prepend it with the "EU" text to avoid the 2nd query that gets the column name.
EDIT: as mentioned in the comments, the shortcoming of this approach is that ordering is done on text rather than numeric values and there's currently no translation for Int32.Parse on SQL.
As you said, there is no translation for IsNumeric from LINQ to SQL. There are a few options, you already wrote database function and stored procedure down. I like to add two more.
Option 1: You can do this by mixing LINQ to SQL with LINQ to Objects, but when you've got a big database, don't expect great performance:
var cols = (from c in db.Table where c.StartsWith("EU") select c).ToList();
var stripped = from c in cols select int.Parse(c.Replace("EU", ""));
var max = stripped.Max();
Option 2: change your database schema :-)
My suggestion is to fall back to in-line SQL and use the DataContext.ExecuteQuery() method. You would use the SQL query you posted in the beginning.
This is what I have done in similar situations. Not ideal, granted, due to the lack of the type-checking and possible syntax errors, but simply make sure it is included in any unit tests. Not every possible query is covered by the Linq syntax, hence the existence of ExecuteQuery in the first place.

Resources