是否可以使用IComparable来比较实体框架中的实体?

时间:2013-09-09 06:11:17

标签: c# .net entity-framework

我有一个实现IComparable的POCO类。

public interface IEntity : IComparable
{
    long Id { get; set; }

    Func<IEntity, bool> CompareFunction { get; }
}

public abstract class BaseEntity : IEntity
{
    public virtual long Id { get; set; }

    public Func<IEntity, bool> CompareFunction
    {
        get
        {
            Func<IEntity, bool> compare = EvaluateEquivalency;
            return compare;
        }
    }

    public static int Compare(BaseEntity left, BaseEntity right)
    {
        if (object.ReferenceEquals(left, right))
        {
            return 0;
        }

        if (object.ReferenceEquals(left, null))
        {
            return -1;
        }

        return left.CompareTo(right);
    }

    public static bool operator ==(BaseEntity left, BaseEntity right)
    {
        if (object.ReferenceEquals(left, null))
        {
            return object.ReferenceEquals(right, null);
        }

        return left.Equals(right);
    }

    public static bool operator !=(BaseEntity left, BaseEntity right)
    {
        return !(left == right);
    }

    public static bool operator <(BaseEntity left, BaseEntity right)
    {
        return Compare(left, right) < 0;
    }

    public static bool operator >(BaseEntity left, BaseEntity right)
    {
        return Compare(left, right) > 0;
    }

    public override bool Equals(object obj)
    {
        IEntity other;

        if (!(obj is IEntity)) return false;

        other = (IEntity)obj;

        if (object.ReferenceEquals(other, null))
        {
            return false;
        }

        return this.CompareTo(other) == 0;
    }

    public override int GetHashCode()
    {
        return base.GetHashCode();
    }

    public virtual int CompareTo(object obj)
    {
        if (obj == null) throw new ArgumentNullException("obj");
        if (!(obj is IEntity)) throw new ArgumentException("obj is not an IEntity");

        if (this.Id == ((IEntity)obj).Id) return 0;

        return -1;
    }

    private bool EvaluateEquivalency(IEntity toCompare)
    {
        return Equals(toCompare);
    }
}

我的DbSet中的POCO课程有DbContext

但是,当我执行BaseRepository.Exists()时,我得到System.NotSupportedException

public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : class, IEntity
{
    ...

    private TEntity Exists(TEntity entity)
    {
        return Context.DbSet<TEntity>.FirstOrDefult(i => i.CompareTo(entity) == 0);
    }

    private TEntity ExistsV2(TEntity entity)
    {
        return Context.DbSet<TEntity>.FirstOrDefult(i => i.CompareFunction(entity) == 0);
    }

    ...
}

异常堆栈跟踪看起来像......

System.NotSupportedException was unhandled by user code
  Message=Unable to create a constant value of type '{My POCO Class}'. Only primitive types or enumeration types are supported in this context.
  Source=System.Data.Entity
  StackTrace:
       at System.Data.Objects.ELinq.ExpressionConverter.ConstantTranslator.TypedTranslate(ExpressionConverter parent, ConstantExpression linq)
       at System.Data.Objects.ELinq.ExpressionConverter.TypedTranslator`1.Translate(ExpressionConverter parent, Expression linq)
       at System.Data.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
       at System.Data.Objects.ELinq.ExpressionConverter.EqualsTranslator.TypedTranslate(ExpressionConverter parent, BinaryExpression linq)
       at System.Data.Objects.ELinq.ExpressionConverter.TypedTranslator`1.Translate(ExpressionConverter parent, Expression linq)
       at System.Data.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
       at System.Data.Objects.ELinq.ExpressionConverter.TranslateLambda(LambdaExpression lambda, DbExpression input)
       at System.Data.Objects.ELinq.ExpressionConverter.TranslateLambda(LambdaExpression lambda, DbExpression input, DbExpressionBinding& binding)
       at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.OneLambdaTranslator.Translate(ExpressionConverter parent, MethodCallExpression call, DbExpression& source, DbExpressionBinding& sourceBinding, DbExpression& lambda)
       at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.FirstPredicateTranslatorBase.Translate(ExpressionConverter parent, MethodCallExpression call)
       at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.SequenceMethodTranslator.Translate(ExpressionConverter parent, MethodCallExpression call, SequenceMethod sequenceMethod)
       at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.TypedTranslate(ExpressionConverter parent, MethodCallExpression linq)
       at System.Data.Objects.ELinq.ExpressionConverter.TypedTranslator`1.Translate(ExpressionConverter parent, Expression linq)
       at System.Data.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
       at System.Data.Objects.ELinq.ExpressionConverter.Convert()
       at System.Data.Objects.ELinq.ELinqQueryState.GetExecutionPlan(Nullable`1 forMergeOption)
       at System.Data.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption)
       at System.Data.Objects.ObjectQuery`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator()
       at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source)
       at System.Data.Objects.ELinq.ObjectQueryProvider.<GetElementFunction>b__1[TResult](IEnumerable`1 sequence)
       at System.Data.Objects.ELinq.ObjectQueryProvider.ExecuteSingle[TResult](IEnumerable`1 query, Expression queryRoot)
       at System.Data.Objects.ELinq.ObjectQueryProvider.System.Linq.IQueryProvider.Execute[S](Expression expression)
       at System.Data.Entity.Internal.Linq.DbQueryProvider.Execute[TResult](Expression expression)
       at System.Linq.Queryable.FirstOrDefault[TSource](IQueryable`1 source, Expression`1 predicate)

当我执行BaseRepository.ExistsV2()时,我的System.NotSupportedException略有不同。

System.NotSupportedException was unhandled by user code
  HResult=-2146233067
  Message=The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
  Source=System.Data.Entity
  StackTrace:
       at System.Data.Objects.ELinq.ExpressionConverter.NotSupportedTranslator.Translate(ExpressionConverter parent, Expression linq)
       at System.Data.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
       at System.Data.Objects.ELinq.ExpressionConverter.TranslateLambda(LambdaExpression lambda, DbExpression input)
       at System.Data.Objects.ELinq.ExpressionConverter.TranslateLambda(LambdaExpression lambda, DbExpression input, DbExpressionBinding& binding)
       at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.OneLambdaTranslator.Translate(ExpressionConverter parent, MethodCallExpression call, DbExpression& source, DbExpressionBinding& sourceBinding, DbExpression& lambda)
       at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.FirstPredicateTranslatorBase.Translate(ExpressionConverter parent, MethodCallExpression call)
       at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.SequenceMethodTranslator.Translate(ExpressionConverter parent, MethodCallExpression call, SequenceMethod sequenceMethod)
       at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.TypedTranslate(ExpressionConverter parent, MethodCallExpression linq)
       at System.Data.Objects.ELinq.ExpressionConverter.TypedTranslator`1.Translate(ExpressionConverter parent, Expression linq)
       at System.Data.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
       at System.Data.Objects.ELinq.ExpressionConverter.Convert()
       at System.Data.Objects.ELinq.ELinqQueryState.GetExecutionPlan(Nullable`1 forMergeOption)
       at System.Data.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption)
       at System.Data.Objects.ObjectQuery`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator()
       at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source)
       at System.Data.Objects.ELinq.ObjectQueryProvider.<GetElementFunction>b__1[TResult](IEnumerable`1 sequence)
       at System.Data.Objects.ELinq.ObjectQueryProvider.ExecuteSingle[TResult](IEnumerable`1 query, Expression queryRoot)
       at System.Data.Objects.ELinq.ObjectQueryProvider.System.Linq.IQueryProvider.Execute[S](Expression expression)
       at System.Data.Entity.Internal.Linq.DbQueryProvider.Execute[TResult](Expression expression)
       at System.Linq.Queryable.FirstOrDefault[TSource](IQueryable`1 source, Expression`1 predicate)

我读过实体框架不支持IComparable?无论如何还是有人知道这个功能是否可以在EF6中使用?

4 个答案:

答案 0 :(得分:3)

不,你不能这样做。

EF要求所有操作都能够执行100%服务器端。支持IComparableIEquatable将要求EF能够将任意IL转换为SQL,而这是不可能的。

否则,它必须将整个未过滤的结果集传递给客户端。您可以使用AsEnumerable()

来实现这一目标
Context.DbSet<TEntity>.AsEnumerable().FirstOrDefault(i => i.CompareTo(entity) == 0);

这当然会运行得很慢,所以如果桌子的尺寸很大,我不会推荐它。

答案 1 :(得分:1)

不幸的是没有(或者更好的答案也不容易)。

实体框架只能使用它知道如何转换为SQL的方法。这在任何版本的SQL中都不会发生变化。如果EF了解ICompariable,那真正意味着它需要能够将任意一段代码转换为SQL。

但是有一些替代方案。您可以定义一个可重用的表达式,该表达式在linq中实现,然后将其应用于SQL中的实体(linq通常可以转换为SQL),这将为您提供类似的行为,但实现起来更复杂。如果你想试试这个http://www.albahari.com/nutshell/linqkit.aspx

,请查看linqkit

答案 2 :(得分:1)

不,这是不可能的,将来也永远不可能,因为lComparable是.Net的一部分,其他地方在SQL中没有这样的东西。 Lambda表达式转换为SQL WHERE子句,它们永远不会被执行。

  x ⇒ x.CustomerID == 2

转换为

 WHERE CustomerID =2 

但是对于您的情况,您可以创建基于反射的方法。

private TEntity Exists(TEntity entity)
{
    Type t= typeof(TEntity) ;
    ParameterExpression pe = Expression.Parameter( t ) ;
    PropertyInfo p = t.GetProperties().First(x => x.GetCustomAttributes( true ).OfType<KeyAttribute>().Any()     ) ;
    Expression e = Expression.Property( pe , p);
    e = Expression.Equal( e, p.GetValue(entity, null) );
    var l = Expression.Lambda<Func <TEntity, boot>>(e , pe) ;
    return Context.DbSet<TEntity>.FirstOrDefult(l);
}

根据您的实现,您可能需要将KeyAttribute更改为EdmScalarDataAttribute&amp;检查属性是否有key = true。

答案 3 :(得分:0)

您可以将基本实体设置为IComparable,而不是比较已加载的实体(通过延迟加载可能是个问题)!实体框架不直接支持IComparable接口。

或者你可以这样做:

public abstract class Entity
{
  public int MyCompare(Entity entity)
  {
   ..
   ..
  }
}

private Entity Exists(Entity entity)
{
    return Context.DbSet<Entity>.ToList().FirstOrDefult(i => i.MyCompare(entity) == 0);
}