views:

90

answers:

0

In my current project, I am using LINQ-to-SQL with a number of entities defined in separate design surfaces, such as:

  • Questions
  • Events
  • Notes

In other design surfaces, I have other entities which can be related to any of the entities above (using a dual foreign key, such as a model type id and the model id itself):

  • Vote
  • Favorite

Then, in LINQ-to-SQL, I can use any DataContext instance and perform joins where necessary. It allows for the following code:

public interface IModel
{
    public int Id { get; set; }
}

...

public class Model<T> where T : IModel
{
    public T Model { get; set; }

    public Vote Vote { get; set; }

    public Favorite { get; set; }
}

public Model<T> GetModel<T>(T model) where T : IModel
{
    // Get model type id.
    int modelTypeId = ...;

    // Get DataContext.
    using (DataContext dc = ...)
    {
        // The query.
        IEnumerable<Model<T>> query =
            from m in dc.GetTable<T>()
                join v in dc.GetTable<Vote>() on
                    new { ModelTypeId = modelTypeId, ModelId = m.Id } equals
                    new { v.ModelTypeId, v.ModelId }
                join f in dc.GetTable<Favorite>() on
                    new { ModelTypeId = modelTypeId, ModelId = m.Id } equals
                    new { f.ModelTypeId, f.ModelId }
            select new Model<T>() {
                Model = m,
                Vote = v,
                Favorite = f,
            };

       // Assume only one.
       return query.Single();
    }
}

The great thing about this is that it allows me to change the entities separately without worrying about changing it in multiple places, and I can access it with any DataContext and it will generate just one query which is sent to the database.

In LINQ-to-Entities, I've been able to hack my way to success, but not without a number of caveats:

  • Assigning a connection string to a design surface generated from a database is pointless, since the point is to mix entity models, but generate one query which is sent to the underlying database. I'm pretty ok with this.

  • However, when not assigning a connection string with separate designer surfaces, you have to go to the ModelBrowser and make sure to change the name of the EntityContainer. The default name for the entity container for a design surface with no connection string associated with it is "Entities" and will cause a compile-time error (assuming the design surfaces are in the same namespace).

  • The "namespace" (on the last page of the wizard) must be different for each designer surface. If it is not, then there is a conflict when trying to load a MetadataWorkspace with all of the entity mapping/storage/conceptual information.

  • I have to create an instance of MetadataWorkspace, specifying the specific entity mapping/storage/concept uris and assemblies (or do what I do now which is to pass "res://*/" for the paths parameter of the MetadataWorkspace constructor). This part is a pain, quite honestly, but I get the feeling I can do it once and then cache it.

  • I have to create the EntityConnection with the MetadataWorkspace. This isn't such a big deal.

Once I've done all of this, I can then transform the above to look like:

public interface IModel
{
    public int Id { get; set; }
}

...

public class Model<T> where T : IModel
{
    public T Model { get; set; }

    public Vote Vote { get; set; }

    public Favorite { get; set; }
}

public Model<T> GetModel<T>(T model) where T : IModel
{
    // Get model type id.
    int modelTypeId = ...;

    // Create the metadata workspaces.
    MetadataWorkspace workspace = new MetadataWorkspace(
        new string[] { "res://*/" },
        new Assembly[] { Assembly.GetExecutingAssembly() });

    // Create the sql connection, entity connection and
    // object context.
    using (SqlConnection sqlConnection = new SqlConnection("..."))
    using (EntityConnection connection = 
        new EntityConnection(workspace, sqlConnection))
    using (ObjectContext context = new ObjectContext(connection))
    {
        // The query.
        IEnumerable<Model<T>> query =
            from m in context.CreateObjectSet<T>()
                join v in context.CreateObjectSet<Vote>() on
                    new { ModelTypeId = modelTypeId, ModelId = m.Id } equals
                    new { v.ModelTypeId, v.ModelId }
                join f in context.GetObjectSet<Favorite>() on
                    new { ModelTypeId = modelTypeId, ModelId = m.Id } equals
                    new { f.ModelTypeId, f.ModelId }
            select new Model<T>() {
                Model = m,
                Vote = v,
                Favorite = f,
            };

       // Assume only one.
       return query.Single();
    }
}

However, it seems that the MetadataWorkspace issue is a little tedious. Is there anything that I'm missing or any way to optimize/reduce the surface of this code (besides the obvious ways)?