伙计们!
假设我们有一组表示问题域的接口: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
,因此可以构建一些复杂的查询并利用延迟加载的所有优点。当我介绍它时,我想到了Linq2Sql将来的应用。
一段时间以来,在开发客户端代码时,我们使用“内存中”实现数据存储。所以一切都很好。但现在是时候将域绑定到 SQL Server 了。因此,我开始在 Linq2Sql 上实现所有基础设施...当制定简单的解决方案时(受到 Fredrik Kalseth 的文章)并得到第一个例外,我已经意识到这个想法的所有悲伤...这是 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; >
因为宗教信仰:)
也许,我应该编写自定义表达式访问者来跳过查询的 AST 调用 IUser.SomeProperty
并将其替换为其内部子 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. 我发现相关问题,其中OP 有一个类似的问题:需要在具体的 ORM 实体和它的域实体之间建立一些桥梁。我发现有趣的 答案:作者建议创建表达式访问者,它将执行实体之间的转换(ORM <=> 域)。
folks!
Suppose we have set of interfaces that presents problem' domain: IUser, IAddressBook, IComment
and so on. The assume that IUser
is defined as follows:
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; }
}
In my application I use only mentioned contracts, for example:
public IUser GetUser(string userName)
{
return Warehouse.GetRepository<IUser>().GetAll()
.First(u => u.Name == userName);
}
As you can see, I'm using some gateway to get data. Repository' method GetAll()
returns IQueryable<TEntity>
, so it is possible to build some complex query and use all benefits of lazy loading. When I was introducing it, I thought about appliance of Linq2Sql in future.
For some time, while developing client code, we were using 'in-memory' implementation of data storage. So everything works fine. But now it's a time to bind domain to SQL Server. So I've started to implement all the infrastructure upon Linq2Sql... And when made simple solution (inspired by article of Fredrik Kalseth) and got first exception, I've realized all sadness of that idea... Here is implmentation of 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
}
And here - exception:
Exception: System.NotSupportedException:
The member 'Data.IHaveName.Name' has no supported translation to SQL.
This is obvious exception - Linq2Sql provider doesn't understand external interfaces, but how should I resolve it? I can't change domain interfaces to return Linq.Expression like:
Expression<Func<string>> IHaveName.Name
{
get { return (() => USERNAME); }
set { USERNAME = value(); }
}
because it breaks all the current code usage of domain interfaces and moreover i can't replace int
with Expression<Func<int>>
because of religious beliefs :)
Maybe, I should write custom Expression visitor to skip in query' AST calling of IUser.SomeProperty
and replace it with it's inner sub-AST...
Can you provide your thoughts on it?
UPDATE. Here I should write something about Repository
implementation. Look at GetAll()
source:
public IQueryable<TEntity> GetAll()
{
ITable table = GetTable();
return table.Cast<TEntity>();
}
protected ITable GetTable()
{
return _dataContext.GetTable(_implType);
}
In my example, TEntity <=> IUser
and _implType <=> typeof(USER)
UPDATE 2. I've found related question, where OP has an alike problem: need some bridge between concrete ORM entities and it's domain entities. And I've found interesting the answer: author suggested to create expression visitor, which will perform convertion between entities (ORM <=> Domain).
发布评论
评论(4)
使用
Column
属性来装饰Name
中的属性,这与用于 Linq2SQL 的任何其他类型相同。Decorate the properties in
Name
withColumn
attributes, the same as you would for any other type you are using with Linq2SQL.这是一个众所周知的问题,每当您尝试使用无法转换为 SQL 字符串命令的逻辑来执行 IQueryable 时,LINQ-to-SQL 本身就会引发此错误。
这个问题也是由于 IQueryable 的本质——延迟执行而出现的。
您可以按如下方式强制执行 IQueryable:
这将确保您的域接口可以保持原样,并且在运行时不会给您带来任何问题。
干杯!
This is a well known problem, LINQ-to-SQL in itself throws this error whenever you try to execute an IQueryable with logic that it cannot cast into a SQL string command.
This problem also arises because of the nature of IQueryable - deferred execution.
You can force execution of the IQueryable as follows:
This will ensure that your domain interfaces can remain as-is and will not give you any problems when running either.
Cheers!
您尝试过使用泛型吗? Linq to SQL 需要知道具体类型,以便它可以将表达式转换为 sql。
Have you tried using generics? Linq to SQL needs to know the concrete type so that it can translate your expression to sql.
看来,我找到了解决问题的方法。今天我发现了不错的名为“使用 Linq 的高级域模型查询”的系列文章。作者提出了他的方法来支持更方便(漂亮)的 Lambda 语法来处理实体。他使用表达式树转换。所以我将遵循这种方法,如果成功,我将发布更详细的答案(也许通过我的博客)。
你觉得这个想法怎么样?我应该花时间实施表达式提供程序吗?
更新。我使用自定义表达式访问失败了 - 它需要通过表达式树进行许多转换。因此,我决定将所有 Linq 逻辑移至“存储”类中,该类封装了复杂的查询和持久性管理。
It seems, I've found a way how to solve my problem. Today I've found nice series of post named "Advanced Domain Model queries using Linq". Author presents his way to support more convenient(pretty) Lambda syntax to work with entities. And he uses Expression Tree convertions. So I will follow this approach, and if I succeed I will post more detailed answer (maybe via my blog).
What do you think about that idea? Should I spend time implementing Expression provider?
UPDATE. I've failed with custom Expression vistior - it requires to many conversion through expression tree. So I've decieded move all Linq logic into "storage" classes, which incapsulates complex querying and persistence managing.