Reputation: 824
I am trying to implement Windows Azure Mobile Service in a Windows Phone library. I have DataContract
defined like this:
[Table]
[DataContract]
public class ToDoCategory : NotifyBase, ISyncableBase
{
[IgnoreDataMember]
[Column(DbType = "INT NOT NULL IDENTITY", IsDbGenerated = true, IsPrimaryKey = true)]
public int LocalId { get; set; }
[Column(CanBeNull = true)]
[DataMember(Name="id")]
public int RemoteId { get; set; }
[Column]
[DataMember]
public bool IsDeleted { get; set; }
[Column]
[DataMember]
public DateTime RemoteLastUpdated { get; set; }
[Column(CanBeNull = true)]
[DataMember]
public DateTime? LocalLastUpdated { get; set; }
}
Then I try to use LINQ expression to load data from the table as follows (unnecessary details removed):
public async void SynchronizeAsync<TEntity>() where TEntity : ISyncableBase
{
var remoteTable = MobileServiceConnection.GetTable<TEntity>();
//Get new entities
var newEntities = await remoteTable.Where(item => item.RemoteLastUpdated > currentTimeStamp).ToListAsync();
}
However, as soon as I come to last line, I get error saying "The member 'RemoteLastUpdated' is not supported in the 'Where' Mobile Services query expression 'Convert(item).RemoteLastUpdated'."
Same is the case for IsDeleted
and even RemoteId
. Apparently, I just can't use Where
operator. Anything I am missing?
Edit: This kind of solved my issue, I don't understand why! Explanation will be helpful. I changed the generic method to following:
public async void SynchronizeAsync<TEntity>() where TEntity : class, ISyncableBase, new()
Notice, I added constrains class
and new
on generic TEntity
and now it works fine. Any idea how this happened?
Upvotes: 4
Views: 873
Reputation: 87308
I see that you were able to unblock yourself by using the class
and new()
constraints. You actually only need the first one - this should work:
public async Task SynchronizeAsync<TEntity>()
where TEntity : class, ISyncableBase { ... }
Now, why one works and the other doesn't the expression generated for the two cases is different. With the class
constraint, this is the expression which is generated for your Where clause:
item => (item.RemoteLastUpdated > currentTimeStamp)
While if you don't have that constraint, this is the expression that is created:
item => (Convert(item).RemoteLastUpdated > currentTimeStamp)
That is because a generic object constrained to be of an interface type can potentially be of a value type, and it needs to be boxed as an object so that we can access its property. And conversions in general aren't supported the the Linq-to-OData converter which is used by the Azure Mobile Services client SDK.
The code below shows the two versions of the expression, and has the version of the generic method which works, only with the class
constraint.
public sealed partial class MainPage : Page
{
public static MobileServiceClient MobileService = new MobileServiceClient("appUrl", "appKey");
public MainPage()
{
this.InitializeComponent();
}
private async void btnStart_Click(object sender, RoutedEventArgs e)
{
AddToDebug("No class constraint:");
NoClassConstraint<ToDoCategory>();
AddToDebug("With class constraint:");
WithClassConstraint<ToDoCategory>();
//await SynchronizeAsync<ToDoCategory>();
}
private void NoClassConstraint<TEntity>() where TEntity : ISyncableBase
{
DumpWhereExpression<TEntity>(item => item.RemoteLastUpdated > DateTime.Now);
}
private void WithClassConstraint<TEntity>() where TEntity : class, ISyncableBase
{
DumpWhereExpression<TEntity>(item => item.RemoteLastUpdated > DateTime.Now);
}
public async Task SynchronizeAsync<TEntity>() where TEntity : class, ISyncableBase
{
try
{
var remoteTable = MobileService.GetTable<TEntity>();
DateTime currentTimeStamp = DateTime.UtcNow.Date;
//Get new entities
var newEntities = await remoteTable.Where(item => ((ISyncableBase)item).RemoteLastUpdated > currentTimeStamp).ToListAsync();
AddToDebug("New entities: {0}", string.Join(" - ", newEntities.Select(e => e.RemoteLastUpdated)));
}
catch (Exception ex)
{
AddToDebug("Error: {0}", ex);
}
}
private void DumpWhereExpression<T>(Expression<Func<T, bool>> predicate)
{
AddToDebug("Predicate: {0}", predicate);
}
void AddToDebug(string text, params object[] args)
{
if (args != null && args.Length > 0) text = string.Format(text, args);
this.txtDebug.Text = this.txtDebug.Text + text + Environment.NewLine;
}
}
public interface ISyncableBase
{
DateTime RemoteLastUpdated { get; set; }
}
[DataContract]
public class NotifyBase { }
public class TableAttribute : Attribute { }
public class ColumnAttribute : Attribute
{
public string DbType { get; set; }
public bool IsDbGenerated { get; set; }
public bool IsPrimaryKey { get; set; }
public bool CanBeNull { get; set; }
}
[Table]
[DataContract]
public class ToDoCategory : NotifyBase, ISyncableBase
{
[IgnoreDataMember]
[Column(DbType = "INT NOT NULL IDENTITY", IsDbGenerated = true, IsPrimaryKey = true)]
public int LocalId { get; set; }
[Column(CanBeNull = true)]
[DataMember(Name = "id")]
public int RemoteId { get; set; }
[Column]
[DataMember]
public bool IsDeleted { get; set; }
[Column]
[DataMember]
public DateTime RemoteLastUpdated { get; set; }
[Column(CanBeNull = true)]
[DataMember]
public DateTime? LocalLastUpdated { get; set; }
}
Upvotes: 3
Reputation: 18362
First: you don't need to use Generics in this case.
Second, Where clause works, since you use the right Table Name.
var table = App.MobileService.GetTable<GameScore>();
var resultList = await table.Where(g => g.Score > 500).ToListAsync();
Upvotes: 0
Reputation: 18362
Does your RemoteLastUpdated column exists in your TEntity class?
try this:
public async void SynchronizeAsync<ToDoCategory>()
{
var remoteTable = MobileServiceConnection.GetTable<ToDoCategory>();
//Get new entities
var newEntities = await remoteTable.Where(item => item.RemoteLastUpdated > currentTimeStamp).ToListAsync();
}
If the code above works, you should move "RemoteLastUpdated" to a class that inherit from ISyncableBase and put this column in there. After that, change your ToDoCategory class to inherit from this new one.
sample:
[DataContract]
public class MobEntity : NotifyBase
{
[Column]
[DataMember]
public DateTime RemoteLastUpdated { get; set; }
}
[Table]
[DataContract]
public class ToDoCategory : MobEntity , ISyncableBase
{
[IgnoreDataMember]
[Column(DbType = "INT NOT NULL IDENTITY", IsDbGenerated = true, IsPrimaryKey = true)]
public int LocalId { get; set; }
[Column(CanBeNull = true)]
[DataMember(Name="id")]
public int RemoteId { get; set; }
[Column]
[DataMember]
public bool IsDeleted { get; set; }
[Column]
[DataMember]
public DateTime RemoteLastUpdated { get; set; }
[Column(CanBeNull = true)]
[DataMember]
public DateTime? LocalLastUpdated { get; set; }
}
and finally:
public async void SynchronizeAsync<MobEntity>() where MobEntity : ISyncableBase
{
var remoteTable = MobileServiceConnection.GetTable<ToDoCategory>();
//Get new entities
var newEntities = await remoteTable.Where(item => item.RemoteLastUpdated > currentTimeStamp).ToListAsync();
}
Upvotes: 1