Linq to SQL:多对多支持类

发布于 2024-10-31 14:41:28 字数 223 浏览 0 评论 0原文

我想要一个支持课程来帮助处理多对多关系。

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

让它允许访问另一个表而不必特别提及关系表应该很容易。然而,在集合中添加或删除有些棘手。您还必须在关系表中添加一行,并根据所做的事情提交或删除它。

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

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

I'd like to have a support class to help with the Many to Many relationship.

As I see it now it could be a double generic class you would define in one or both of the entity partial classes surrounding the relationship.

Getting it to allow access to the other table without having to specifically mention the relationship table should be easy. However adding or removing from the collection is somewhat trickier. You would ahve to add a row into the relationship table aswell and commit it or remove it based on what is done.

Could this be done through a function that is passed into this generic class?

Does a class like this exists and if not is it something that can be viably done?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

天涯离梦残月幽梦 2024-11-07 14:41:28

您可以创建一个可从多对多属性返回的 IManyToManySet 接口,并通过查询插入和实现 ManyToManySet 实现删除特征。

接口可能如下所示:

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
  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 接口实际上是多余的,因为您可以直接返回 ManyToMany 。然而,该接口隐藏了 TSourceTCross 类型参数,这使得该属性的用户更具可读性。

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

然而,重要的区别在于 LINQ to SQL 理解 LINQ 查询中的 EntitySet属性。在 LINQ 查询中使用 IManyToManySet 将会严重失败。

我希望这有帮助。

You can create a IManyToManySet<TEntity> interface that can be returned from your many to many property and have a ManyToManySet<TSource, TCross, TDestination> implementation with the query insert and delete features.

The interface may look like this:

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);
}

And the implementation might look like this:

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();
    }
}

You need to supply a couple of things in this implementation:

  1. The TSource instance, that points back at the entity that defines the property.
  2. The EntitySet<TCross> that points to the list of entities that define the cross table.
  3. A projection function that allows you to convert a TCross to the TDestination.
  4. A factory function that allows you to create a new TCross based on the TSource and TDestination.

Translating this to a practical example (Using Product and Order), would give you the following property in the Order entity:

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;
    }
}

And the following property in the Product entity:

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;
    }
}

The IManyToManySet<T> interface is in fact redundant, because you can return a ManyToMany<TSource, TCross, TDestination> directly. The interface however hides the TSource and TCross type arguments, which makes it a bit more readable to the user of this property.

Note that this implementation has the same loading behavior as LINQ to SQL's EntitySet<T>; When it is used, it loads the complete set of objects in memory. Just as with an EntitySet<T> using a where or First on the collection, still loads the complete collection from the database. You need to be aware of that.

Important difference is however that LINQ to SQL understands EntitySet<T> properties within LINQ queries. Having a IManyToManySet<T> inside a LINQ query will fail miserably.

I hope this helps.

春花秋月 2024-11-07 14:41:28

在 LINQ to 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 本身支持此功能。

It's hard (perhaps even impossible) to create a solution in LINQ to SQL that feels like something native, because such a solution must work in the following ways:

  1. The many collection should be modeled as a property on the other many entity.
  2. It must support inserts, updates and deletes.
  3. It should work when writing LINQ queries.

It is easy to make a solution for point 1. Take for instance a model with a Product class with a many-to-many relationship with Order. You could define the following property on the Order class:

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

This however doesn't work with point 2 and 3. While we could make a generic collection that allows inserting, LINQ to SQL will never be able to translate the use of this property back into a SQL query. For instance, the following LINQ query looks innocent:

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

Unfortunately, this query will fail, because LINQ to SQL doesn't know how to map the Products property to SQL.

When you want this feature natively, you should consider migrating to Entity Framework (4.0 or up). EF supports this natively.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文