Linq to Entities - 需要通过PK动态过滤

时间:2012-06-19 09:59:46

标签: c# entity-framework-4 linq-to-entities

我正试图找到一种方法,如何只从给定的表中获取PK列在给定的对象列表中。

不要问我为什么要这样做。这是为了同步多个数据库,我想为预定的主键预加载所有本地值,然后插入,更新或删除它们。

在第一次尝试时,我使用动态反射加载了整个表,并返回一个字典,表PK为Key,EntityObject为值,这是非常好的。但对于较大的桌子,这将变得非常沉重。所以我只需要给定PK的那些。

private static Dictionary<object, EntityObject> GetAllEntities<TEntityType>(ObjectContext dbContext, IEnumerable<object> primaryValues) where TEntityType : EntityObject
    {
        // loop through the elements for the given entity
        return dbContext.CreateObjectSet<TEntityType>().ToDictionary(type => type.EntityKey.EntityKeyValues[0].Value, type => (EntityObject)type); // this runs fine

        // this is what i need to be executed
        return dbContext.CreateObjectSet<TEntityType>().Where(type => primaryValues.Contains(type.EntityKey.EntityKeyValues.First().Value)).ToDictionary(type => type.EntityKey.EntityKeyValues[0].Value, type => (EntityObject)type); // this crashes
    }

第二行按预期抛出错误,因为Linq 2实体无法直接处理EntityKey:

  

LINQ to Entities中不支持指定的类型成员'EntityKey'。仅支持初始化程序,实体成员和实体导航属性。

我确信我已经在互联网上的某个地方找到了一种方式,但我无法再找到它。 我记得的是,那个最初在某处发布它的人在where子句中调用了一个返回EntityObjects PK值的方法,所以我可以检查给定列表中是否存在。

任何帮助,提示,链接,以及任何值得赞赏的内容。

这可能是错误的问题,也可能是处理这些案件的正确方法。所以任何事情都可能有所帮助。

其他一些信息: 每个表只有一个PK,可以是Guid或string类型。所以这将是一个简单的字符串到字符串检查相等。

1 个答案:

答案 0 :(得分:0)

这比这复杂一点。这里的问题是linq查询被转换为SQL。实体框架只能转换为包含“已知”类型的SQL表达式 - 即模型中的类型。 EntityKey不是类似的类型,因此是异常。你想要做的仍然是可能的,但需要一些Linq表达式和反射魔法。我想出了一个像这样的方法:

    private static IQueryable<TEntity> DynamicContains<TEntity, TProperty>(IQueryable<TEntity> query, Expression<Func<TEntity, TProperty>> property, IEnumerable<TProperty> values)
    {
        var memberExpression = property.Body as MemberExpression; 
        if (memberExpression == null || !(memberExpression.Member is PropertyInfo)) 
        { 
            throw new ArgumentException("Property expression expected", "property"); 
        }

        // get the generic .Contains method
        var containsMethod =
            typeof(Enumerable)
            .GetMethods()
            .Single(m => m.Name == "Contains" && m.GetParameters().Length == 2);

        // convert the generic .Contains method so that is matches the type of the property
        containsMethod = containsMethod.MakeGenericMethod(typeof(TProperty));

        // build e => Enumerable.Contains(values, e.Property)
        var lambda = 
            Expression.Lambda<Func<TEntity, bool>>(
                Expression.Call(
                    containsMethod, Expression.Constant(values), property.Body), 
                    property.Parameters.Single());

        // return query.Where(e => Enumerable.Contains(values, e.Property))
        return query.Where(lambda);
    }

它是一个通用包含您的实体可能拥有的任何属性(不仅是键)。它需要IQueryable,因此可以应用于任何查询(不仅仅是DbSet / ObjectSet)并返回IQueryable,以便您可以在其上进一步构建。不要被所有尖括号吓到。你可以像这样使用这种方法:

var entities = DynamicContains(ctx.EntitySet, e => e.Id, new[] { 1, 4 });

e =&gt; e.Id是一种花哨的方式来告诉我们应该使用什么属性包含。

以下是在野外显示此方法的完整示例:

    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    using System.Text;
    using System.Threading.Tasks;

    namespace ConsoleApplication3
    {
        class MyEntity
        {
            public int Id { get; set; }
            public string Description { get; set; }
        }


        class MyContext : DbContext
        {
            public DbSet<MyEntity> EntitySet { get; set; }
        }

        class Program
        {

            private static void Seed()
            {
                using (var ctx = new MyContext())
                {
                    if (!ctx.EntitySet.Any())
                    {
                        ctx.EntitySet.Add(new MyEntity() { Description = "abc" });
                        ctx.EntitySet.Add(new MyEntity() { Description = "xyz" });
                        ctx.EntitySet.Add(new MyEntity() { Description = null });
                        ctx.EntitySet.Add(new MyEntity() { Description = "123" });
                        ctx.SaveChanges();
                    }
                }
            }

            private static void PrintEntities(IEnumerable<MyEntity> entities)
            {
                foreach (var e in entities)
                {
                    Console.WriteLine("Id: {0}, Description: {1}", e.Id, e.Description);
                }
            }

            static void Main(string[] args)
            {
                List<int> list = new List<int>() { 1, 3 };

                Seed();

                using (var ctx = new MyContext())
                {
                    PrintEntities(DynamicContains(ctx.EntitySet, e => e.Id, new[] { 1, 4 }));
                    PrintEntities(DynamicContains(ctx.EntitySet, e => e.Description, new[] { null, "xyz" }));

                }
            }

            private static IQueryable<TEntity> DynamicContains<TEntity, TProperty>(IQueryable<TEntity> query, Expression<Func<TEntity, TProperty>> property, IEnumerable<TProperty> values)
            {
                var memberExpression = property.Body as MemberExpression; 
                if (memberExpression == null || !(memberExpression.Member is PropertyInfo)) 
                { 
                    throw new ArgumentException("Property expression expected", "property"); 
                }

                // get the generic .Contains method
                var containsMethod =
                    typeof(Enumerable)
                    .GetMethods()
                    .Single(m => m.Name == "Contains" && m.GetParameters().Length == 2);

                // convert the generic .Contains method so that is matches the type of the property
                containsMethod = containsMethod.MakeGenericMethod(typeof(TProperty));

                // build e => Enumerable.Contains(values, e.Property)
                var lambda = 
                    Expression.Lambda<Func<TEntity, bool>>(
                        Expression.Call(
                            containsMethod, Expression.Constant(values), property.Body), 
                            property.Parameters.Single());

                // return query.Where(e => Enumerable.Contains(values, e.Property))
                return query.Where(lambda);
            }
        }
    }