【问题标题】:How to make the Linq2Sql understand custom types?如何让 Linq2Sql 理解自定义类型?
【发布时间】:2011-12-15 15:24:15
【问题描述】:

伙计们!

假设我们有一组呈现问题域的接口:IUser, IAddressBook, IComment 等等。假设IUser 定义如下:

public interface IUser : IAmIdentifiedEntity<int>, IHaveName
{
    string FullName { get; set; }
    string Email { get; set; }
    bool ReceiveNotification { get; set; }
}

public interface IHaveName
{
    string Name { get; set; }
}

在我的应用程序中,我只使用提到的合约,例如:

public IUser GetUser(string userName)
{
    return Warehouse.GetRepository<IUser>().GetAll()
        .First(u => u.Name == userName);
}

如您所见,我正在使用一些网关来获取数据。 Repository 的方法GetAll() 返回IQueryable&lt;TEntity&gt;,因此可以构建一些复杂的查询并使用延迟加载的所有好处。在介绍的时候,我想到了 Linq2Sql 以后的应用。

有一段时间,在开发客户端代码时,我们一直在使用“内存中”实现数据存储。所以一切正常。但现在是时候将域绑定到 SQL Server 了。所以我开始在 Linq2Sql 上实现所有基础设施......当制作简单的解决方案(受 Fredrik Kalseth 的 article 启发)并遇到第一个异常时,我意识到了所有的悲伤想法...这是IUser的实现:

public partial class USER : IUser
{
    int IHaveID<int>.ID
    {
        get { return USERID; }
    }

    string IHaveName.Name
    {
        get { return USERNAME;  }
        set { USERNAME = value; }
    }

    string IUser.FullName 
    {
        get { return USERFULLNAME; }
        set { USERFULLNAME = value; }
    }

    // ... same approach for other properties
}

这里 - 例外:

Exception: System.NotSupportedException: 
    The member 'Data.IHaveName.Name' has no supported translation to SQL.

这是一个明显的例外——Linq2Sql 提供者不理解外部接口,但我应该如何解决呢?我无法更改域接口以返回 Linq.Expression,例如:

Expression<Func<string>> IHaveName.Name
{
    get { return (() => USERNAME);  }
    set { USERNAME = value(); }
}

因为它破坏了域接口的所有当前代码使用,而且由于宗教信仰,我不能用 Expression&lt;Func&lt;int&gt;&gt; 替换 int :)

也许,我应该编写自定义表达式访问者以跳过 IUser.SomeProperty 的查询 AST 调用并将其替换为它的内部子 AST...

你能提供你的想法吗?

更新。 在这里我应该写一些关于Repository 实现的内容。看GetAll()来源:

public IQueryable<TEntity> GetAll()
{
    ITable table = GetTable();
    return table.Cast<TEntity>();
}

protected ITable GetTable()
{
    return _dataContext.GetTable(_implType);
}

在我的示例中,TEntity &lt;=&gt; IUser_implType &lt;=&gt; typeof(USER)

更新 2. 我发现 related question,其中 OP 有一个类似的问题:需要在具体的 ORM 实体和它的域实体之间建立一些桥梁。而且我发现了有趣的the answer:作者建议创建表达式访问者,它将执行实体之间的转换(ORM 域)。

【问题讨论】:

    标签: c# linq linq-to-sql domain-driven-design ddd-repositories


    【解决方案1】:

    Column 属性装饰Name 中的属性,就像使用Linq2SQL 使用的任何其他类型一样。

    【讨论】:

    • 你的意思是IHaveName,当写Name时?
    • 不,我的意思是名称,实际的 Linq2SQL 类将在其上查找属性。
    • ...虽然它可能位于基类中的属性而不是接口上,但在某些情况下可以解决此问题。
    • 您的建议对我不起作用...我不知道 Linq2SQl 是如何实现的,但在我的示例中,我认为它寻找接口 IHaveName 的绑定,而不是实现界面......无论如何它都会抛出相同的异常
    • GetRepository 调用了什么,用户或 IUser 上的 GetTable?我一直在构建我的返回具体类型,这就是我在这里的想法。
    【解决方案2】:

    这是一个众所周知的问题,当您尝试使用无法转换为 SQL 字符串命令的逻辑执行 IQueryable 时,LINQ-to-SQL 本身就会引发此错误。

    由于 IQueryable 的性质 - 延迟执行,也会出现此问题。

    您可以按如下方式强制执行 IQueryable:

    public IUser GetUser(string userName)
    {
        return Warehouse.GetRepository<IUser>().GetAll()
            .AsEnumerable()
            .First(u => u.Name == userName);
    }
    

    这将确保您的域接口可以保持原样,并且在运行时也不会给您带来任何问题。

    干杯!

    【讨论】:

    • 问题不是 IQueryable 的延迟执行(您也应该使用 AsEnumerable() 延迟),而是确定如何执行它的查询引擎是不同的。 Linq2SQL 需要有关如何针对不是对象定义固有的数据库进行计算的信息。这绕过了它,但需要扫描整个表的大部分甚至全部以查找任何请求。
    • @JoHanna:是的,完全正确。我经常使用 .NET MVC,因此发现使用这种方法更简单,并在应用程序级别使用 SQL 依赖项缓存有问题的表。列属性不能解决这个问题,我知道的唯一其他选择是 IQueryable 中的扩展,它将附加参数作为字段插入到通过数据库生成的 LINQ-to-SQL 类中。
    • 因此,如果有 30000 个用户并且匹配的用户是最后一个用户,那么您将从数据库中检索 29999 个您不使用的用户?当然,您可以将表缓存在内存中,甚至可以通过用户名上的字典来缓存,以便可以在几乎恒定的时间内检索它,但这会带来缓存失效的所有问题。
    • @JonHanna: 是的,由于数据集非常大和高频 CRUD 操作,缓存失效成为一种永远存在的危险。我发布了一个关于 LINQ 执行速度的问题——它确实达不到标准。 LINQ 虽然是架构的神器,但在查询优化方面实际上还远未成熟。我的另一种方法是写出我自己的所有模型,并结合运行时绑定到 LINQ 类,并实现一个自定义属性,该属性将任何类成员标记为“DoesNotBelongToDBTable”,允许具有基于业务逻辑字段的模型。
    • @VarunVohra:不要认为这对我来说是一个解决方案。这将需要比我预期的更多的工作......此外,我有这样一个用于缓存/无效数据的工具 - DataContext,我只需要使用域接口和基础设施来适应它......现在我做不到:(
    【解决方案3】:

    您是否尝试过使用泛型? Linq to SQL 需要知道具体的类型,以便它可以将您的表达式转换为 sql。

    public TUser GetUser<TUser>(string userName) where TUser : IUser
    {
        return Warehouse.GetRepository<TUser>().GetAll()
            .First(u => u.Name == userName);
    }
    

    【讨论】:

    • 首先,应用程序对Linq2Sql类一无所知,所以不可能看到GetRepository&lt;User&gt;()这样的表达...
    • 然后你必须做一些从你的应用程序类到你的 Linq2Sql 类型的映射。我知道,它并不漂亮,但像 AutoMapper 这样的工具可以让生活更轻松。
    【解决方案4】:

    看来,我找到了解决问题的方法。今天我发现了一个不错的 series of post,名为“使用 Linq 的高级域模型查询”。作者介绍了他的方法来支持更方便(漂亮)的 Lambda 语法来处理实体。他使用表达式树转换。所以我会遵循这个方法,如果我成功了我会发布更详细的答案(可能通过我的博客)。

    你觉得这个想法怎么样?我应该花时间实现表达式提供程序吗?

    更新。我在自定义表达式访问者方面失败了 - 它需要通过表达式树进行多次转换。所以我决定将所有 Linq 逻辑移到“存储”类中,它封装了复杂的查询和持久性管理。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-12
      • 1970-01-01
      • 1970-01-01
      • 2021-04-28
      • 2012-03-07
      • 2015-07-27
      相关资源
      最近更新 更多