1

我正在开发一个 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 应用程序开发人员针对外观编写的 LINQ 查询转换为客户端 WCF 数据服务代理可以转换为 OData URL 的 LINQ 查询。

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

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' 方法的源参数必须是导航或集合属性。” 我很确定这是因为Any()正在调用 aModelCollection<T1,T2>而不是DataServiceCollection<T>它的 InnerCollection,但我不知道该怎么做。

4

1 回答 1

1

The question is long (good question though) but the answer is short. You can't expose IQueryable and expect that its leaky abstractions won't hurt you.

In the end the interface is backed by DataServiceQuery<T>. There's a long list of LINQ methods that are not supported: LINQ Considerations (WCF Data Services): see Unsupported LINQ Methods. Besides that, DataServiceQuery has instance methods that are useful, but can't be exploited by your interface, like Expand.

I'm afraid this requires a major overhaul of your architecture: expose methods that accept specifications and translate these to supported linq queries behind the façade.

于 2013-05-29T20:04:25.397 回答