0

我正在尝试找到一种方法,如何仅从给定表中获取那些 PK 列在给定对象列表中的元素。

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

在第一次尝试时,我使用动态反射加载了整个表并返回了一个字典,其中表 PK 作为键,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 Entities 无法直接处理 EntityKey:

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

我敢肯定,几个月前我已经在互联网上的某个地方找到了一种方法,但我再也找不到了。我只记得,最初将它发布在某处的人在 where 子句中调用了一个方法,该方法返回了 EntityObjects PK 值,因此我可以检查给定列表中是否存在。

任何帮助,提示,链接,任何将不胜感激。

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

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

4

1 回答 1

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);
    }

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

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

e => 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);
            }
        }
    }
于 2012-06-20T18:27:43.770 回答