views:

726

answers:

3

I've been trying to encapsulate the object mapping in a projects data repository. Perhaps EF will provide the level of abstraction required but for a range of reasons I am using Linq to SQL at the moment. The following code aims to return the users in the database as a list of ModUser objects, where ModUser is POCO that the repository exposes:

public List<ModUser> GetUsers() {
    Users.Select(MapUser).ToList();
}

public Expression<Func<User, ModUser>> MapUser {
    get {
        return u => new ModUser() {
            UserId = u.User_Id,
            UserResources = u.Resources(MapResource)
        }
    }
}

public Expression<Func<Resource, ModResource>> MapResource { ...

The code will fail as I can't call the MapResource expression since I am trying to call it from inside another expression. I have managed to get around this by replacing 'MapResource' with u => new ModResource(), then using the ExpressionVisitor to find this placeholder node and replace it with the MapResource expression.

I also have similar issues when I try to assign a property of ModUser with an expression involving a single property, i.e. UserResource = MapResource. I have managed to get around this second issue by manually combining the expressions required using the methods on the Expression class.

I do realise that I could change the code above to

UserResources = u.Resources(r => MapResource.Compile().Invoke(r));

But then the final SQL query produced will need to obtain all the attributes of r, not just the ones needed by MapResouce, since we are now dealing with a function. Also, should MapResouce require access to further tables on r it won't be possible since it is being used as a function not an expression. I could set DeferredLoadingEnabled to true but that would spawn off a multitude of individual queries rather than modifying the main query to join with whatever tables are required.

Does anyone know if these operations are going to become easier in future versions of .NET or am I going about this the wrong way? I really like the Linq and Expression features, I just wish I could employ them using more readable code.

Updated

Thought I might add some examples of how I have made the expressions more composable. They aren't concise, but they get the job done.

public Expression<Func<User, ModUser>> MapUser {
    get {
        Expression<Func<User, ModUser>> mapUser = u => new ModUser() {
            UserId = u.User_Id,
            UserResources = u.Resources(r => new ModResource())
        };
        return mapUser.MapResources(this);
    }
}

public Expression<Func<Resource, ModResource>> MapResource { ... }


public static Expression<Func<T0, T1>> MapResources<T0, T1>(this Expression<Func<T0, T1>> exp, DataContext dc) {
    return exp.Visit<MethodCallExpression, Expression<Func<T0, T1>>>(m => {
        if(m.Arguments.Count > 1 && m.Arguments[1].Type == typeof(Func<DataContext.Resource, ModResource>)) { //Find a select statement that has the sub expression as an argument
            //The resource mapping expression will require the Resource object, which is obtained here
            ParameterExpression resourceParam =  ((LambdaExpression)m.Arguments[1]).Parameters[0];
            return Expression.Call(m.Method, m.Arguments[0], //The first argument is the record selection for the 'select' method
                Expression.Lambda<Func<DataContext.Resource, ModResource>>(//Provide the proper mapping expression as the projection for the 'select' method
                     Expression.Invoke(dc.MapResource, resourceParam),
                     resourceParam)
                );
        }
        return m;
    });
}

So what am I doing here? Note that in this version of MapUser I don't create the ModResource object correctly, I just create a dummy version. I then call an expression visitor method that looks for the dummy call and replaces it with the one I originally wanted in there. To me it seems like the expression syntax is lacking as I am able to essentially construct the expression tree I originally wanted, but I have to actually vist the tree to do it. Below is another workaround I've found that deals with the singular case:

public Expression<Func<User, ModUser>> MapUser {
    get {
        Expression<Func<User, ModResource, ModUser>> mapUser = (u, resource) => new ModUser() {
            UserId = u.User_Id,
            UserResource = resource;
        }

        return mapUser.CollapseArgument(MapResource, user => user.MainResource);
    }
}

public Expression<Func<Resource, ModResource>> MapResource { ... }

public static Expression<Func<T0, T3>> CollapseArgument<T0, T1, T2, T3>(this Expression<Func<T0, T1, T3>> exp, Expression<Func<T2, T1>> exp0, Expression<Func<T0, T2>> exp1) {
    var param0 = Expression.Parameter(typeof(T0), "p0");
    var argExp = Expression.Invoke(exp0, Expression.Invoke(exp1, param0));
    return Expression.Lambda<Func<T0, T3>>(
         Expression.Invoke(exp, param0, argExp),
         param0);
}

In this second example I know that I can obtain the Resource data from the User data but I can't "inline" an expression to show how to do this and map the Resource data to a resource POCO. But I can manually create an expression tree that is given an already mapped resource POCO and use it. I can then create another expression that shows how to obtain the resource raw data from the user and a final expression that shows how to map the raw resource data into a resource POCO. It is now conceivable that I can combine all of this information into a single expression tree in a way that "collapses" out the resource specific parameter since I can obtain it from the primary user parameter. This is what the code above does.

So I've found ways to make expressions highly composable... It just doesn't feel clean.

A: 

I think if you want to use POCOs, Linq to SQL is not the best choice. I think you'd probably be a lot better off using something like NHibernate. Using Linq to SQL with POCOs means that you are building a layer on top of the data layer (Linq to SQL) on top of the database. With NHibernate, you would be building all your code and mapping it straight to the database. Less layers == less code == less work.

Chris Shaffer
@chris haven't used nhibernate for quite some time, but isn't that "code" moved to mappings in xml files?
eglasius
Use Fluent NHibernate - then you get code instead of xml, with all the refactoring/compile time verification that you'd expect.
Chris Shaffer
I'm really looking for a Linq to SQL solution here as moving to NHibernate isn't an option for this project.
Brehtt
I think you're missinformed about Linq2Sql- it's just an ado.net datareader to a POCO
Scott Weinstein
I suppose it is; However, NHibernate lets you define the POCO. Linq to SQL builds it for you, and tacks on a bunch of additional features that can be good but also cause problems (eg, serializing and deserializing a "POCO" from L2S is not straightforward).
Chris Shaffer
@Chris: you're conflating the Linq-to-SQL *designer* with Linq-to-SQL. It's perfectly feasible to use Linq-to-SQL without the designer, and bind POCO objects to your database using System.Data.Linq.
Portman
+1  A: 

The way Linq To SQL supports POCO is a bit different.

To achieve persistence ignorance, you would use a mapping file that describes how a modUser is mapped (columns, associations etc), and not the LTS designer. When you create a new context, you pass it the XML mapping file as an XMLMappingSource.

This way, LTS will return your objects from the database.

I've read here and there that defining your collection association properties as Read/Write properties of type IList(of T) is enough for LinqToSQL to provide lazy loading on those collections, but I have not tried it, so I cannot vouch for it.

Entity Framework will be even worse for POCO support in its current version (basically none as far as most people understand the term POCO).

All the usual LTS limitations apply to this, so no "value Object" mapping. If you want something a bit farther removed from the database AND POCO support, then you need to look at NHibernate.

Denis Troller
A mapping file isn't going to have the expressive power that I require. The only way I can imagine doing this is to use a series of composable expressions. I actually have this working and I'm about to update the post to show an example. It's just not as clean as I would like.
Brehtt
+1  A: 

Ok I have to admit I did not really finish reading the OP's question (sheepish grin) but did you know that you can use Linq-to-SQL attributes to decorate any POCO object? You don't have to use the designer.

Here's a random example from code that is open in front of me right now. This is a POCO called "Product" that has some attributes applied to it that will let it interact with a Linq-to-SQL DataContext.

HTH

using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Linq;
using System.Web;

namespace Redacted.Site.Models.Store
{
    /// <summary>
    /// A "Product" is a good for purchase at the store.
    /// </summary>
    [Table(Name = "s.products")]
    public partial class Product
    {
        /// <summary>Gets or sets the PK of the object/row.</summary>
        [Column(Name = "id", IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL")]
        public Int32 ID { get; set; }

        /// <summary>Gets or sets the Title.</summary>
        [Column(Name = "title", DbType = "NVARCHAR(500) NOT NULL")]
        public String Title { get; set; }

        /// <summary>Gets or sets the Lede.</summary>
        [Column(Name = "lede", DbType = "NVARCHAR(MAX) NOT NULL")]
        public String Lede { get; set; }

        /// <summary>Gets or sets the Description.</summary>
        [Column(Name = "description", DbType = "NTEXT NOT NULL")]
        public String Description { get; set; }

        /// <summary>Gets or sets the Price.</summary>
        [Column(Name = "price", DbType = "FLOAT NOT NULL")]
        public Double Price { get; set; }

        /// <summary>Gets or sets the FK to the <see cref="Department"/>.</summary>
        [Column(Name = "department_id", DbType = "TINYINT NOT NULL")]
        public Byte DepartmentID { get; set; }

        /// <summary>Gets or sets the date/time the product was released to the store.</summary>
        [Column(Name = "released_on_utc", DbType = "DATETIME NOT NULL")]
        public Int32 ReleasedOnUtc { get; set; }

    }
}
Portman
Is it possible to use this approach to populate POCO's that derive their attributes from multiple tables, sometimes through non-trivial relationships? My initial response is that this approach will only work for very simple mappings. Do you have any links or examples of more complex scenarios?
Brehtt
Sure. The L2S designer is just a code generator. There's no magic. So you can do anything the L2S designer can do in your own code, with your own generator. I'm on the road but will amend this with more complex example next week.
Portman
My concern is that I haven't been able to achieve what I want through the designer and have had to find more expressive means. But I'll wait and check out the more complex examples once they are up, thanks.
Brehtt