如何针对包装WCF数据服务的外观编写LINQ查询?

时间:2013-05-29 18:58:27

标签: .net linq expression wcf-data-services

我正在开发一个Silverlight应用程序从WCF数据服务中提取数据,但是与服务的通信位于外观之后。我这样做是为了在没有实现服务器的情况下促进SL应用程序的开发,因此SL应用程序可以使用来自txt文件的数据而不知道其中的差异。

下面的接口构成了外观,具体类是它们在外观后面的实现。

public interface IDataContext
{
    IEntityQuery<IRootEntity> Roots { get; }
    IEntityQuery<IBranchEntity> Branches{ get; }
    IEntityQuery<ILeafEntity> Leaves { get; }
}

public interface IEntityQuery<TEntity> : IQueryable<TModel>
{
    IAsyncResult BeginExecute(AsyncCallback callback, object state);
    IEnumerable<TModel> EndExecute(IAsyncResult asyncResult);
    IEntityQuery<TModel> Where(Expression<Func<TModel, bool>> predicate);
}

public interface IEntityCollection<TEntity> : INotifyCollectionChanged,
    INotifyPropertyChanged, IQueryable<TModel>
{

}

public interface IRootEntity
{
    int Id { get; set; }
    string Name { get; set; }
    IModelCollection<IBranchEntity> Branches { get; set; }
}

public interface IBranchEntity
{
    int Id { get; set; }
    string Name { get; set; }
    IRootEntity Root { get; set; }
    IModelCollection<ILeafEntity> Leaves { get; set; }
}

public interface ILeafEntity
{
    int Id { get; set; }
    string Name { get; set; }
    IBranchEntity Branch { get; set; }
}

// Partially implemented by me and partially by Visual Studio when the Service
// Reference was added.
public partial class Container : IDataContext
{
    IEntityQuery<IRootEntity> IDataContext.Roots 
    { 
        get 
        { 
            return new ModelQuery<IRootEntity, RootEntity>(this.Roots); 
        }
    }

    IEntityQuery<IBranchEntity> IDataContext.Branches
    { 
        get 
        { 
            return new ModelQuery<IBranchEntity, BranchEntity>(this.Roots); 
        }
    }

    IEntityQuery<ILeafEntity> IDataContext.Leaves
    { 
        get 
        { 
            return new ModelQuery<ILeafEntity, LeafEntity>(this.Leaves); 
        }
    }
}

public class EntityQuery<TFacade, TConcrete>
    where TConcrete : class, TFacade
{
    private DataServiceQuery<TConcrete> _dsq;

    public ModelQuery(DataServiceQuery<TConcrete> dsq)
    {
        _dsq = dsq;
    }

    public IAsyncResult BeginExecute(AsyncCallback callback, object state)
    {
        return _dsq.BeginExecute(callback, state);
    }

    public IEnumerable<TFacade> EndExecute(IAsyncResult asyncResult)
    {
        return _dsq.EndExecute(asyncResult).AsEnumerable() as IEnumerable<TFacade>;
    }

    public IModelQuery<TFacade> Where(Expression<Func<TFacade, bool>> predicate)
    {
        Expression<Func<TConcrete, bool>> concretePredicate = Expression.Lambda<Func<TConcrete, bool>>(predicate.Body, predicate.Parameters);
        return new ModelQuery<TFacade, TConcrete>(_dsq.Where(concretePredicate) as DataServiceQuery<TConcrete>);
    }

    // IQueryable implementation ...
}

public class EntityCollection<TFacade, TConcrete> : IEntityCollection<TFacade>
    where TConcrete : class, TFacade
{
    public EntityCollection(ObservableCollection<TConcrete> innerCollection)
    {
        this.InnerCollection = innerCollection;
        this.InnerCollection.CollectionChanged += InnerCollection_CollectionChanged;
    }

    internal ObservableCollection<TConcrete> InnerCollection { get; private set; }

    private void InnerCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        this.OnCollectionChanged(e);
    }

    // IQueryable<TFacade> implementation ...
}

// Partially implemented by me and partially by Visual Studio when the Service
// Reference was added.
public partial class RootEntity : IRootEntity
{   
    IList<IBranchEntity> IRootEntity.Branches
    {
        get { return this.Branches; }
        set { this.Branches = value as IList<IBranchEntity>; }
    }
}

// Partially implemented by me and partially by Visual Studio when the Service
// Reference was added.
public partial class BranchEntity : IBranchEntity 
{   
    IRootEntity IBranchEntity.Root
    {
        get { return this.Root; }
        set { this.Root = value as RootEntity; }
    }

    IList<ILeafEntity> IBranchEntity.Leaves
    {
        get { return this.Leaves; }
        set { this.Leaves = value as IList<LeafEntity>; }
    }
}

// Partially implemented by me and partially by Visual Studio when the Service
// Reference was added.
public partial class LeafEntity : ILeafEntity 
{   
    IRootEntity ILeafEntity.Root
    {
        get { return this.Root; }
        set { this.Root = value as RootEntity; }
    }
}

EntityQueryEntityCollection类对于维护外观的抽象是必要的。没有它们,SL应用程序必须知道DataServiceQueryDataServiceCollection

我遇到的问题是将SL应用程序开发人员针对Facade编写的LINQ查询转换为LINQ查询,客户端WCF数据服务代理可以将其转换为OData URL。

我已经能够运行一些简单的查询,但更复杂的查询会开始抛出异常。目前给我一个问题的查询如下:

IEntityQuery<IRootEntity> query = this.Context.Roots
    .Where(r => r.Branches.Any(b=> b.Leaves.Any(l => l.Name == "Find Me")));

IRootEntity result = Task.Factory.StartNew(() => query.BeginExecute(null, null))
    .ContinueWith(t => query.EndExecute(t.Result))
    .Result
    .Single();

我得到一个NotSupportedException声明“'Any'方法的source参数必须是导航或集合属性。”我很确定这是因为Any()是在ModelCollection<T1,T2>而不是DataServiceCollection<T>上调用的,因为它是InnerCollection,但我不知道该怎么办。

1 个答案:

答案 0 :(得分:1)

问题很长(虽然问题很好),但答案很短。您无法公开IQueryable并期望其leaky abstractions不会伤害您。

最后,界面由DataServiceQuery<T>支持。有一长串不支持的LINQ方法:LINQ Considerations (WCF Data Services): see Unsupported LINQ Methods。除此之外,DataServiceQuery具有有用的实例方法,但不能被您的界面利用,例如Expand

我担心这需要对您的架构进行重大改革:公开接受规范的方法并将这些方法转换为支持的linq查询幕后。