如何让Linq2Sql了解自定义类型?

时间:2011-10-26 10:48:00

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

人们!

假设我们有一组接口提出问题'domain: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);
}

正如您所看到的,我正在使用一些网关来获取数据。存储库'方法GetAll()返回IQueryable<TEntity>,因此可以构建一些复杂的查询并使用延迟加载的所有好处。当我介绍它的时候,我想到了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(); }
}

因为它打破了域接口的所有当前代码使用,而且由于宗教信仰,我无法用int替换Expression<Func<int>>:)

也许,我应该编写自定义Expression访问者来跳过查询'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 <=> IUser_implType <=> typeof(USER)

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

4 个答案:

答案 0 :(得分:0)

使用Name属性装饰Column中的属性,与使用Linq2SQL的任何其他类型相同。

答案 1 :(得分:0)

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

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

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

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

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

干杯!

答案 2 :(得分:0)

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

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

答案 3 :(得分:0)

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

您如何看待这个想法?我应该花时间实现Expression provider吗?

UPDATE。我使用自定义Expression vistior失败了 - 它需要通过表达式树进行多次转换。所以我已经决定将所有Linq逻辑移到“存储”类中,它们包含了复杂的查询和持久性管理。