我正试图找到一种方法,如何只从给定的表中获取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类型。所以这将是一个简单的字符串到字符串检查相等。
答案 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);
}
}
}