1

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).

Community
  • 1
  • 1
ajukraine
  • 561
  • 9
  • 27

4 Answers4

0

Decorate the properties in Name with Column attributes, the same as you would for any other type you are using with Linq2SQL.

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
  • Did you mean `IHaveName`, when wrote `Name` ? – ajukraine Oct 26 '11 at 11:10
  • No, I mean Name, the actual class Linq2SQL will be finding the property on. – Jon Hanna Oct 26 '11 at 11:18
  • ... though it could be on a property in a base class rather than an interface, which gets around this issue in *some* cases. – Jon Hanna Oct 26 '11 at 12:49
  • Your suggestion doesn't work to me... I don't know how Linq2SQl is implemented, but in my example I think that it looks for binding for interface `IHaveName`, not to implmentation of the interface... Anyway it throws same exception – ajukraine Oct 26 '11 at 16:27
  • What does the `GetRepository` call into, a GetTable on User or IUser? I've always built mine to return concrete types, which is what I was thinking of here. – Jon Hanna Oct 26 '11 at 16:30
  • That's right. I call GetTable() with implementation type (User in this concrete case), but then call `table.Cast()` – ajukraine Oct 26 '11 at 17:03
  • Outside of ways I've done it myself then, so I'm out of ideas. I'll leave my answer here since maybe someone with a better idea can use the above conversation to help you more. – Jon Hanna Oct 26 '11 at 17:20
0

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:

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

This will ensure that your domain interfaces can remain as-is and will not give you any problems when running either.

Cheers!

vvohra87
  • 5,594
  • 4
  • 22
  • 34
  • 2
    It's not the deferred execution of IQueryable that's the issue, (you would have deferred with AsEnumerable() too) but that the query engine that works out just how to execute it is different. Linq2SQL needs information about how to compute against the database that isn't inherent to the object definition. This gets around it, but requires much or even all of the entire table to be scanned for ever request. – Jon Hanna Oct 26 '11 at 11:41
  • @JoHanna: Yes, that's exactly right. I work a lot with .NET MVC and as such find it simpler to use this approach and cache the tables in question at the application level with SQL dependencies. The column attribute does not solve this issue, the only other alternative I know of is an extension in IQueryable which inserts the additional parameter as a field in the LINQ-to-SQL class generated via database. – vvohra87 Oct 26 '11 at 12:03
  • So if there are 30000 users and the matching user was the last one you're going to retrieve 29999 users from the database that you don't use? Granted you can cache the table in memory and perhaps even by a dictionary on username so it can be retrieved in near constant time, but this brings on all the issues of cache invalidation. – Jon Hanna Oct 26 '11 at 12:48
  • @JonHanna: Yes, with extremely large data-sets and high frequency CRUD operations cache invalidation becomes an ever present danger. I had posted a question regarding speed of execution of LINQ - it really isn't up to the mark. LINQ while being a god send for architecture, is actually far from maturity with respect to query optimization. Another approach I had was writing out all my own Models coupled with a run-time bind to the LINQ classes and implementing a custom attribute which flagged any class member as "DoesNotBelongToDBTable" allowing for models with business logic based fields. – vvohra87 Oct 26 '11 at 13:45
  • @VarunVohra: Don't think that it's a solution for me. It will require much more work than I've expected... Furthemore I have such a tool for caching/invalidation of data - DataContext, and only what I need is to adapt that with domain interfaces and infrastructure... And for now I can't do it :( – ajukraine Oct 26 '11 at 16:31
0

Have you tried using generics? Linq to SQL needs to know the concrete type so that it can translate your expression to sql.

public TUser GetUser<TUser>(string userName) where TUser : IUser
{
    return Warehouse.GetRepository<TUser>().GetAll()
        .First(u => u.Name == userName);
}
jrummell
  • 42,637
  • 17
  • 112
  • 171
  • First of all, application knows nothing about Linq2Sql classes, so it's impossible to see such expression as `GetRepository()`... – ajukraine Oct 26 '11 at 17:12
  • Then you'll have to do some kind of mapping from your application classes to your Linq2Sql types. Its not pretty, I know, but tools like AutoMapper can make life easier. – jrummell Oct 26 '11 at 17:16
0

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.

ajukraine
  • 561
  • 9
  • 27