blfuentes
blfuentes

Reputation: 2827

Linq over generic class with generic property

The issue is about getting the Max value within a sequence of elements. The type of the object is unknown and the property follows the convention <EntityName>_Id.

At the moment I resolved it like this:

public static int GetMaxId<TEntity>()
{
    try
    {
        var key = typeof(TEntity).Name;
        var adapter = (IObjectContextAdapter)MyDbContext;
        var objectContext = adapter.ObjectContext;
        // 1. we need the container for the conceptual model
        var container = objectContext.MetadataWorkspace.GetEntityContainer(
            objectContext.DefaultContainerName, System.Data.Entity.Core.Metadata.Edm.DataSpace.CSpace);
        // 2. we need the name given to the element set in that conceptual model
        var name = container.BaseEntitySets.Where((s) => s.ElementType.Name.Equals(key)).FirstOrDefault().Name;
        // 3. finally, we can create a basic query for this set
        var query = objectContext.CreateQuery<TEntity>("[" + name + "]");

        // working with result
        var baseEntity = container.BaseEntitySets.Where((s) => s.ElementType.Name.Equals(key)).FirstOrDefault();
        var property = baseEntity.ElementType.Members.FirstOrDefault(_property => _property.Name.EndsWith("Id"));

        var tmpResult = query.ToList();

        var currentMaxID = tmpResult.Max(element =>
        {
            PropertyInfo pInfo = typeof(TEntity).GetProperty(property.Name);
            int value = (int)pInfo.GetValue(element);

            return value;
        });

        return currentMaxID;
    }
    catch (Exception ex)
    {
        string exMessage = "GetMaxId.Exception";
        EntityModelDataProviderLogger.Fatal(exMessage, ex);
        throw;
    }
}

Is there a better/cleaner way to do it?

I have read about dynamic Expressions but it wasn't so clear to me.

LINQ expression with generic property This example compares Dates. In my case I have "only one element" and I don't know how to construct the expressions.

Upvotes: 0

Views: 687

Answers (2)

jimSampica
jimSampica

Reputation: 12410

I would define an interface with property Id and add a constraint on your method to only accept types of that interface. That removes the need for fragile naming conventions and makes reflection unnecessary.

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

public static int GetMaxId<TEntity>() where TEntity : IEntity
{
    return MyDbContext.Set<TEntity>().AsEnumerable().Max(e => e.Id);
}

public Entity : IEntity
{
    public int MyId { get; set;}

    public int Id { get{ return MyId; } set{ MyId = value; } }
}

Usage:

var max = GetMaxId<Entity>();

Upvotes: 0

xanatos
xanatos

Reputation: 111820

It is quite long, and I can't test it here:

public static int GetMaxId<TEntity>()
{
    try
    {
        var key = typeof(TEntity).Name;
        var adapter = (IObjectContextAdapter)MyDbContext;
        var objectContext = adapter.ObjectContext;

        // 1. we need the container for the conceptual model
        var container = objectContext.MetadataWorkspace.GetEntityContainer(
            objectContext.DefaultContainerName, System.Data.Entity.Core.Metadata.Edm.DataSpace.CSpace);
        // 2. we need the name given to the element set in that conceptual model
        var baseEntity = container.BaseEntitySets.Single(s => s.ElementType.Name == key);
        // 3. finally, we can create a basic query for this set
        var query = objectContext.CreateQuery<TEntity>("[" + baseEntity.Name + "]");

        // Looking for the property
        string propertyId = baseEntity.Name + "_" + "Id";
        // The PropertyInfo connected to the EdmMember
        PropertyInfo property = (PropertyInfo)typeof(TEntity).GetProperty(propertyId);

        // Building the Expression
        ParameterExpression par = Expression.Parameter(typeof(TEntity));
        MemberExpression prop = Expression.Property(par, property);

        // cast to (int?)property
        // Note the use of int?, because the table could be empty!
        UnaryExpression conv = Expression.Convert(prop, typeof(int?));

        // An expression like x => x.Entity_Id
        Expression<Func<TEntity, int?>> lambda = Expression.Lambda<Func<TEntity, int?>>(conv, par);

        int? currentMaxID = ((IQueryable<TEntity>)query).Max(lambda);

        // We change null to 0
        return currentMaxID ?? 0;
    }
    catch (Exception ex)
    {
        string exMessage = "GetMaxId.Exception";
        EntityModelDataProviderLogger.Fatal(exMessage, ex);
        throw;
    }
}

If it works I'll explain how/why it works...

Note that what you were doing is conceptually wrong... You where SELECTing all the table with the ToList() and then locally looking for the Max(). This code instead creates an Expression Tree to do the Max() database-side.

Upvotes: 1

Related Questions