Alias properties in LINQ to SQL - linq-to-sql

I'm using a shim property to make sure that the date is always UTC. This in itself is pretty simple but now I want to query on the data. I don't want to expose the underlying property, instead I want queries to use the shim property. What I'm having trouble with is mapping the shim property. For example:
public partial class Activity
{
public DateTime Started
{
// Started_ is defined in the DBML file
get{ return Started_.ToUniversalTime(); }
set{ Started_ = value.ToUniversalTime(); }
}
}
var activities = from a in Repository.Of<Activity>()
where a.Started > DateTime.UtcNow.AddHours( - 3 )
select a;
Attempting to execute the query results in an exception:
System.NotSupportedException: The member 'Activity.Started' has no supported
translation to SQL.
This makes sense - how could LINQ to SQL know how to treat the Started property - it's not a column or association? But, I was looking for something like a ColumnAliasAttribute that tells SQL to treat properties of Started as Started_ (with underscore).
Is there a way to help LINQ to SQL translate the expression tree to the Started property can be used just like the Started_ property?

There's a code sample showing how to do that (i.e. use client-side properties in queries) on Damien Guard's blog:
http://damieng.com/blog/2009/06/24/client-side-properties-and-any-remote-linq-provider
That said, I don't think DateTime.ToUniversalTime will translate to SQL anyway so you may need to write some db-side logic for UTC translations anyway. In that case, it may be easier to expose the UTC date/time as a calculated column db-side and include in your L2S classes.
E.g.:
create table utc_test (utc_test_id int not null identity,
local_time datetime not null,
utc_offset_minutes int not null,
utc_time as dateadd(minute, 0-utc_offset_minutes, local_time),
constraint pk_utc_test primary key (utc_test_id));
insert into utc_test (local_time, utc_offset_minutes) values ('2009-09-10 09:34', 420);
insert into utc_test (local_time, utc_offset_minutes) values ('2009-09-09 22:34', -240);
select * from utc_test

Based on #KrstoferA's answer I came up with a reliable solution that hides the fact that the properties are aliased from client code. Since I'm using the repository pattern returning an IQueryable[T] for specific tables, I can simply wrap the IQueryable[T] result provided by the underlying data context and then translate the expression before the underlying provider compiles it.
Here's the code:
public class TranslationQueryWrapper<T> : IQueryable<T>
{
private readonly IQueryable<T> _source;
public TranslationQueryWrapper( IQueryable<T> source )
{
if( source == null ) throw new ArgumentNullException( "source" );
_source = source;
}
// Basic composition, forwards to wrapped source.
public Expression Expression { get { return _source.Expression; } }
public Type ElementType { get { return _source.ElementType; } }
public IEnumerator<T> GetEnumerator() { return _source.GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
// Intercept calls to the provider so we can translate first.
public IQueryProvider Provider
{
get { return new WrappedQueryProvider(_source.Provider); }
}
// Another wrapper around the provider
private class WrappedQueryProvider : IQueryProvider
{
private readonly IQueryProvider _provider;
public WrappedQueryProvider( IQueryProvider provider ) {
_provider = provider;
}
// More composition
public object Execute( Expression expression ) {
return Execute( expression ); }
public TResult Execute<TResult>( Expression expression ) {
return _provider.Execute<TResult>( expression ); }
public IQueryable CreateQuery( Expression expression ) {
return CreateQuery( expression ); }
// Magic happens here
public IQueryable<TElement> CreateQuery<TElement>(
Expression expression )
{
return _provider
.CreateQuery<TElement>(
ExpressiveExtensions.WithTranslations( expression ) );
}
}
}

Another example cannot hurt I guess.
In my Template class, I have a field Seconds that I convert to TimeStamp relatively to UTC time. This statement also has a CASE (a?b:c).
private static readonly CompiledExpression<Template, DateTime> TimeStampExpression =
DefaultTranslationOf<Template>.Property(e => e.TimeStamp).Is(template =>
(template.StartPeriod == (int)StartPeriodEnum.Sliding) ? DateTime.UtcNow.AddSeconds(-template.Seconds ?? 0) :
(template.StartPeriod == (int)StartPeriodEnum.Today) ? DateTime.UtcNow.Date :
(template.StartPeriod == (int)StartPeriodEnum.ThisWeek) ? DateTime.UtcNow.Date.AddDays(-(int)DateTime.UtcNow.DayOfWeek) : // Sunday = 0
(template.StartPeriod == (int)StartPeriodEnum.ThisMonth) ? new DateTime(DateTime.UtcNow.Year, DateTime.UtcNow.Month, 1, 0, 0, 0, DateTimeKind.Utc) :
(template.StartPeriod == (int)StartPeriodEnum.ThisYear) ? new DateTime(DateTime.UtcNow.Year, 1, 1, 0, 0, 0, DateTimeKind.Utc) :
DateTime.UtcNow // no matches
);
public DateTime TimeStamp
{
get { return TimeStampExpression.Evaluate(this); }
}
My query to initialize a history-table based on (Event.TimeStamp >= Template.TimeStamp):
foreach (var vgh in (from template in Templates
from machineGroup in MachineGroups
let q = (from event in Events
join vg in MachineGroupings on event.MachineId equals vg.MachineId
where vg.MachineGroupId == machineGroup.MachineGroupId
where event.TimeStamp >= template.TimeStamp
orderby (template.Highest ? event.Amount : event.EventId) descending
select _makeMachineGroupHistory(event.EventId, template.TemplateId, machineGroup.MachineGroupId))
select q.Take(template.MaxResults)).WithTranslations())
MachineGroupHistories.InsertAllOnSubmit(vgh);
It takes a defined maximum number of events per group-template combination.
Anyway, this trick sped up the query by four times or so.

Related

Sort an Array of MovieClips on values within their child clips

I have a class called Table that is linked to a clip on the stage.
During setup I create many Table instances and pass each one an instance of a TableData class. This TableData object includes an ID value.
I put a selection of my Table objects in an Array which I would lik eto sort based on the ID value within each Table's instance of TableData.
What I had hoped to use was something like: myArray.sortOn("tableData.id");
This doesn't seem to work and I assume that Array.sortOn() can't drill down into child clips.
Is there a way that I can achieve this?
The Array and generic Vector.<> collections both provide an alternate form of the .sort() method which takes a function. You can provide your own sorting implementation, in this case which peeks objects and compares only specific pieces of them.
Consider the following class:
class TableData {
public var ID:String;
public function TableData(id:String):void {
this.ID = id;
}
}
This is a simplified version of your TableData class that only provides a ID. Then a simplified Table class:
class Table {
private var _data:TableData;
public function get data():TableData {
return _data;
}
public function Table(tableData:TableData) {
_data = tableData;
}
}
Finally, lets put them to use, and spin up a new collection.
var list:Vector.<Table> = new Vector.<Table>();
list.push(new Table(new TableData("X")));
list.push(new Table(new TableData("C")));
list.push(new Table(new TableData("S")));
list.push(new Table(new TableData("A")));
Now, we need to make a new function that will actually do our comparison:
private function sortUsingDataID(a:Table, b:Table):int {
var aID:String = a.data.ID;
var bID:String = b.data.ID;
return aID < bID ? aID == bID ? 0 : -1 : 1;
}
This function will expect to get two items, and should return either -1, 0, or 1. If a should come before b then the function should return -1. Otherwise, if a should come after b, then it should return 1. Lastly, if the two are the same, then it should return a value of 0.
So, now we just need to tell the array to resort itself using our custom sorting function by calling .sort and passing in our function.
list.sort(sortUsingDataID);
Now the list should be sorted the way that you want.
See an working example here.
See the documentation for more details

Dapper And System.Data.OleDb DbType.Date throwing 'OleDbException : Data type mismatch in criteria expression'

Not sure if I should raise an issue regarding this, so thought I would ask if anybody knew a simple workaround for this first. I am getting an error when I try to use Dapper with OleDbConnection when used in combination with MS Access 2003 (Jet.4.0) (not my choice of database!)
When running the test code below I get an exception 'OleDbException : Data type mismatch in criteria expression'
var count = 0;
using (var conn = new OleDbConnection(connString)) {
conn.Open();
var qry = conn.Query<TestTable>("select * from testtable where CreatedOn <= #CreatedOn;", new { CreatedOn = DateTime.Now });
count = qry.Count();
}
I believe from experience in the past with OleDb dates, is that when setting the DbType to Date, it then changes internally the value for OleDbType property to OleDbTimeStamp instead of OleDbType.Date. I understand this is not because of Dapper, but what 'could' be considered a strange way of linking internally in the OleDbParameter class
When dealing with this either using other ORMs, raw ADO or my own factory objects, I would clean up the command object just prior to running the command and change the OleDbType to Date.
This is not possible with Dapper as far as I can see as the command object appears to be internal. Unfortunately I have not had time to learn the dynamic generation stuff, so I could be missing something simple or I might suggest a fix and contribute rather than simply raise an issue.
Any thoughts?
Lee
It's an old thread but I had the same problem: Access doesn't like DateTime with milliseconds, so you have to add and extension method like this :
public static DateTime Floor(this DateTime date, TimeSpan span)
{
long ticks = date.Ticks / span.Ticks;
return new DateTime(ticks * span.Ticks, date.Kind);
}
And use it when passing parameters:
var qry = conn.Query<TestTable>("select * from testtable where CreatedOn <= #CreatedOn;", new { CreatedOn = DateTime.Now.Floor(TimeSpan.FromSeconds(1)) });
Unfortunately, with current Dapper version (1.42), we cannot add custom TypeHandler for base types (see #206).
If you can modify Dapper (use the cs file and not the DLL) merge this pull request and then you do not have to use Floor on each parameters :
public class DateTimeTypeHandler : SqlMapper.TypeHandler<DateTime>
{
public override DateTime Parse(object value)
{
if (value == null || value is DBNull)
{
return default(DateTime);
}
return (DateTime)value;
}
public override void SetValue(IDbDataParameter parameter, DateTime value)
{
parameter.DbType = DbType.DateTime;
parameter.Value = value.Floor(TimeSpan.FromSeconds(1));
}
}
SqlMapper.AddTypeHandler<DateTime>(new DateTimeTypeHandler());

BLToolkit MapValue not mapping

We are starting to do a conversion to BL Toolkit but are hitting some issues and not finding answers. One such issue is the inability to get the MapValue attribute on our DTO's properly to map.
Using T4 templates, we generate this (as an example):
[MapField("counterparty_fl")]
[MapValue(true, 'y')]
[MapValue(false, 'n')]
public bool CounterpartyFlag { get; set; } // flag_yn_TY(1)
Our database is Sybase, and the field counterparty_fl is a char(1) that accepts either 'y' or 'n'.
However, when I look at the SQL generated by the following link query, it is writing [counterparty_fl] = 0. What I need is [counterparty_fl] = 'n'
var results = (from i in facade.InputList
where (
i.UserIdentifier == criteria.UserId &&
i.CounterpartyFlag == false &&
i.Name == criteria.Name)
select i);
Has anyone had better luck with MapValue? Any suggestions?
This type of mapping is not supported for linq queries. The problem is that CounterpartyFlag field can be mapped to 'y', 'n' values, but 'false' in your expression can't be.
You can use enum for CounterpartyFlag field type:
public enum
{
[MapValue('y')] Yes,
[MapValue('n')] No
}
This should work.

How to avoid "source !=null" when using Code Contracts and Linq To Sql?

I have the following code using a normal data context which works great:
var dc = new myDataContext();
Contract.Assume(dc.Cars!= null);
var cars = (from c in dc.Cars
where c.Owner == 'Jim'
select c).ToList();
However when I convert the filter to an extension method like this:
var dc = new myDataContext();
Contract.Assume(dc.Cars!= null);
var cars = dc.Cars.WithOwner('Jim');
public static IQueryable<Car> WithOwner(this IQueryable<Car> cars, string owner)
{
Contract.Requires(cars != null);
return cars.Where(c => c.Owner == owner);
}
I get the following warning:
warning : CodeContracts: requires unproven: source != null
My guess is that your warning is caused by the owner parameter, rather than the cars. Add a precondition in the WithOwner method to check if owner is not null.
public static IQueryable<Car> WithOwner(IQueryable<Car> cars, string owner)
{
Contract.Requires(cars != null);
Contract.Requires(!string.isNullOrEmpty(owner));
return cars.Where(c => c.Owner = owner);
}
In your first code sample, you have 'Jim' hard-coded, so no problems there because there is not something which can be null.
In your second example you created a method for which the static compiler cannot prove that the source ( being owner ) 'will never be null', as other code might call it with an invalid values.
I wonder how you get the code compiled with the Extension method since you are missing this keyword in your method signature.
public static IQueryable<Car> WithOwner(this IQueryable<Car> cars, string owner)
{
...
}
/KP
Its possible that your code snippet does not completely describe the code you are using.
Consider this snippet instead:
var dc = new myDataContext();
Contract.Assume(dc.Cars!= null);
var models = dc.Cars.WithOwner('Jim').Select(c => c.Model);
public static IQueryable<Car> WithOwner(this IQueryable<Car> cars, string owner)
{
Contract.Requires(cars != null);
return cars.Where(c => c.Owner == owner);
}
In this snipped its likely the runtime will complain with the warning you mentioned, but it is not complaining about Cars possibly being null, it is complaining about the result from WithOwner (passed into Select) possibly being null.
You can satisfy the runtime by ensuring that the result from your extension method will not be null:
Contract.Ensures(Contract.Result<IQueryable<Car>>() != null);
This contract should be ok because Where will not return null, but instead returns an Enumerable.Empty<T>() when there are no matches.
We fixed this a few releases back. The warning was due to some missing contracts around Linq expression construction etc. Linq expression methods have contracts and the C# compiler generates code that calls these methods. If we don't have enough post conditions on the called methods, then you can get these cryptic warnings about code that you don't even know is there (unless you look with ILdasm).

Using Linq to update a List<T> of objects to DB

I have a form that returns me a List of FlatSessie objects
in my edit view I edit a few FlatSessie's and get them returned to my Post method in that List object.
In my DB I have Sessies, which I map using Automapper to FlatSessie's and back
now I can not get linq to make the update to the DB for me.
the code:
[HttpPost]
public ActionResult Sessies(int id, int? modID, int? projID, string schooljaarparam, List<FlatSessie> sl) {
if (ModelState.IsValid) {
foreach (FlatSessie s in sl) { //i run over all FlatSessies which i get
Models.Sessies ses = Mapper.Map<FlatSessie, Sessies>(s); // i map them to the Sessies object
List<Sessies> origReeks = _db.Sessies.Where(p => p.Ses_ID == ses.Ses_ID).ToList(); //i get the original Session by ID. if there is a Session with that ID, if not (the ID will be 0) i do an Insert. if there is i want to do an Update.
if (origReeks.Count > 0) {
//it's an update
UpdateModel(origReeks.First(); //doesnt work
//_db.Sessies.Attach(ses, origReeks.First()); //doesnt work, gives me an error on used ID...
_db.SubmitChanges();
} else {
//no sessies yet, add them, this works.
_db.Sessies.InsertOnSubmit(ses);
_db.SubmitChanges();
}
}
TempData["okmsg"] = "De sessies zijn opgeslagen";
return RedirectToAction("Index");
}
//if not valid, i return the viewdata which i need.
Module m = _db.Modules.First(md => md.Mod_ID == modID.Value);
int antses = m.Mod_AantalSessies.Value;
List<List<SelectListItem>> lpllst = new List<List<SelectListItem>>(antses);
for (int i = 0; i < antses; i++) {
lpllst.Add(MvcApplication.lesplaatsList(schooljaarparam, -1));
}
ViewData["lesplist"] = lpllst;
ViewData["lglist"] = MvcApplication.lesgeverList();
return View(sl);
}
It might work to provide a prefix to UpdateModel (FlatSessie[n], where n is such that it matches the actual input name of the model element in question) so that it knows which properties to map onto the object, but because you are getting a list of these it might not. Have you tried updating the retrieved model using the data from the matching FlatSessie object directly?
Also, once you get this to work, you might want to do a single SubmitChanges for all inserts/updates (outside the loop) so that you get the entire submit wrapped in a single transaction. This will make it easier if there are errors to correct them and resubmit -- since you won't have some changes already committed causing further potential errors.