2011-04-12 99 views
0

我想要一个支持类来帮助多对多关系。Linq to SQL:多对多支持类

正如我现在看到的那样,它可能是一个双重泛型类,您可以在一个或两个围绕关系的实体局部类中定义它。

获得它允许访问其他表而不必特别提及关系表应该很容易。然而,添加或从集合中删除有点棘手。您也可以在关系表中添加一行,并根据所做的操作将其提交或删除。

这可以通过传递到这个泛型类的函数来完成吗?

像这样的类是否存在,如果不存在,是否可以完成?

回答

2

您可以创建一个IManyToManySet<TEntity>接口,该接口可以从您的多个属性返回并具有ManyToManySet<TSource, TCross, TDestination>实现以及查询插入和删除功能。

的界面看起来可能是这样:

public interface IManyToManySet<TEntity> : IEnumerable<TEntity> 
    where TEntity : class 
{ 
    int Count { get; } 
    void Add(TEntity entity); 
    bool Remove(TEntity entity); 
    void AddRange(IEnumerable<TEntity> collection); 
} 

和实现可能是这样的:

public class ManyToManySet<TSource, TCross, TDestination> 
    : IManyToManySet<TDestination>, IEnumerable<TDestination> 
    where TDestination : class 
    where TSource : class 
    where TCross : class 
{ 
    private TSource source; 
    private EntitySet<TCross> crossSet; 
    private Func<TCross, TDestination> destinationSelector; 
    private Func<TSource, TDestination, TCross> crossFactory; 

    public ManyToManySet(TSource source, 
     EntitySet<TCross> crossSet, 
     Func<TCross, TDestination> destinationSelector, 
     Func<TSource, TDestination, TCross> crossFactory) 
    { 
     this.source = source; 
     this.crossSet = crossSet; 
     this.destinationSelector = destinationSelector; 
     this.crossFactory = crossFactory; 
    } 

    public int Count 
    { 
     get { return this.crossSet.Count; } 
    } 

    public void Add(TDestination entity) 
    { 
     var newEntity = this.crossFactory(this.source, entity); 
     this.crossSet.Add(newEntity); 
    } 

    public bool Remove(TDestination entity) 
    { 
     var existingEntity = (
      from c in this.crossSet 
      where this.destinationSelector(c) == entity 
      select c) 
      .SingleOrDefault(); 

     if (existingEntity != null) 
     { 
      return this.crossSet.Remove(existingEntity); 
     } 

     return false; 
    } 

    public void AddRange(IEnumerable<TDestination> collection) 
    { 
     foreach (var entity in collection) 
     { 
      this.Add(entity); 
     } 
    } 

    public IEnumerator<TDestination> GetEnumerator() 
    { 
     return this.crossSet.Select(this.destinationSelector) 
      .GetEnumerator(); 
    } 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return this.GetEnumerator(); 
    } 
} 

您需要提供几件事情在此实现:

  1. TSource实例,该实例指向定义属性的实体。
  2. 指向定义交叉表的实体列表的EntitySet<TCross>
  3. 投影功能,可让您将TCross转换为TDestination
  4. 工厂功能允许您根据TSourceTDestination创建新的TCross

翻译这一个实际的例子(使用ProductOrder),会给你的Order实体以下属性:

private IManyToManySet<Product> products; 
public IManyToManySet<Product> Products 
{ 
    get 
    { 
     if (this.products != null) 
     { 
      this.products = new ManyToManySet<Order, OrderProduct, Product>(
       this, this.OrderProducts, op => op.Product, 
       (o, p) => new OrderProduct { Order = o, Product = p }); 
     } 

     return this.products; 
    } 
} 

而下面的属性在Product实体:

private IManyToManySet<Order> orders; 
public IManyToManySet<Order> Orders 
{ 
    get 
    { 
     if (this.orders == null) 
     { 
      this.orders = new ManyToManySet<Product, OrderProduct, Order>(
       this, this.OrderProducts, op => op.Order, 
       (p, o) => new OrderProduct { Order = o, Product = p }); 
     } 

     return this.orders; 
    } 
} 

IManyToManySet<T>接口实际上是多余的,因为您可以直接返回ManyToMany<TSource, TCross, TDestination>。然而,该接口隐藏了TSourceTCross类型参数,这使得该属性的用户可读性更高。

请注意,此实现具有与LINQ to SQL的EntitySet<T>相同的加载行为;当它被使用时,它会将完整的一组对象加载到内存中。就像在集合上使用whereFirstEntitySet<T>一样,仍然从数据库加载完整的集合。你需要意识到这一点。

不过,LINQ to SQL理解LINQ查询中的EntitySet<T>属性。在LINQ查询中有一个IManyToManySet<T>将失败。

我希望这会有所帮助。

+0

我有一个小问题是删除关系,因为该操作试图在交叉表中将外键清零,而不是仅删除该行。我发现我可以在关联上设置一个DeleteOnNull,以确保当从父项目entitySet中移除时该行将被删除。 – 2011-04-13 14:03:57

+0

当一个计划聚集在一起时,我喜欢它! – Steven 2011-04-13 14:34:57

1

很难(甚至不可能)创建LINQ到SQL,感觉像一些本土的解决方案,因为这样的解决方案必须在以下几个方面工作:

  1. 许多收集应建模为另一个很多实体的财产。
  2. 它必须支持插入,更新和删除。
  3. 它应该在编写LINQ查询时工作。

很容易为第1点做出解决方案。例如,带有Product类的模型与Order具有多对多关系。您可以定义在Order类以下属性:

public IEnumerable<Product> Products 
{ 
    get { return this.OrderProducts.Select(op => op.Product); } 
} 

然而,这并不与点2和3的工作,而我们可以做一个普通的集合,它允许插入,LINQ to SQL的将永远无法翻译将此属性用于SQL查询。例如,下面的LINQ查询看起来无辜:

var bigOrders = 
    from order in context.Orders 
    where order.Products.Any(p => p.Price > 100) 
    select order; 

不幸的是,这个查询将失败,因为LINQ to SQL的不知道如何Products属性SQL映射。

如果您本地需要此功能,则应考虑迁移到实体框架(4.0或更高版本)。 EF本身支持这一点。

+0

好的我将在下一个项目中使用EF。但是,让我们说我不想以有限的方式对待另一个。基本上得到关系另一端的对象列表(例子中的产品),并能够直接插入,更新和删除那些会自动创建关系的集合。这可以通过将Order类中的函数传递到处理添加的自定义集合类来完成吗? – 2011-04-12 11:50:54

+0

@Ingó:看到我的第二个答案。我写了一个可以做到这一点的泛型类的实现。 – Steven 2011-04-12 15:14:33