Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LINQ operater Where not supporting any of the members when used inside generic method

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?

like image 354
akshay2000 Avatar asked Oct 25 '25 12:10

akshay2000


1 Answers

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; }
}
like image 64
carlosfigueira Avatar answered Oct 27 '25 01:10

carlosfigueira