在Entity Framework中检查对象是否存在的最佳方法是什么?

125

从性能角度来看,检查对象是否存在于数据库中的最佳方法是什么?我正在使用Entity Framework 1.0(ASP.NET 3.5 SP1)。

8个回答

257

如果您不想直接执行SQL语句,最好的方法是使用Any()。这是因为当它找到匹配项时就会立即返回。另一个选项是Count(),但是这可能需要在返回之前检查每一行。

以下是如何使用它的示例:

if (context.MyEntity.Any(o => o.Id == idToMatch))
{
    // Match!
}

还有在vb.net中

If context.MyEntity.Any(function(o) o.Id = idToMatch) Then
    ' Match!
End If

在VB中,如果(context.MyEntity.Any(o => o.Id <> idToMAtch)),那么就是匹配!抱歉,这不是代码标记,我不知道如何做到这一点! - Kevin Morrissey
@virtouso 在执行查询之前,请先进行.Any()检查。我认为没有API调用可以做到这一点,但您可以编写自己的辅助方法。 - Alex Angas
我正在尝试编写 protected bool IsExists(int id){ return T.Any(c => c.Id.Equals(id)); } 但是 c.Id 给了我一个错误,因为它是一个泛型类。我想到的另一种方法是 protected bool IsExists(int id){ return T.Find(id) != null ; }。但我想使用前者。如何解决? - phougatv
使用 Any 似乎仍会从数据库中获取所有值。使用 Find() != null 更轻量级。 - Neville Nazerane
1
@barnes 如果你将 T 限制为一个包含 Id 的对象并实现了 IEnumerable 接口,那么你应该能够使用你的泛型函数 IsExists<T>() - Suncat2000
显示剩余4条评论

9

是的,好主意,但我只能使用之前版本的实体框架。 - Freddy

5
我曾经管理过这样一种场景:新数据记录中提供的重复百分比非常高,因此需要进行许多千次数据库调用来检查重复(因此CPU大量时间被占用)。最终我决定将最后的 100,000 条记录缓存在内存中。这样我就可以根据缓存记录快速检查重复,而不是使用针对 SQL 数据库的 LINQ 查询,然后将任何真正的新记录写入数据库(同时将它们添加到数据缓存中,我也会对其进行排序和修剪以使其长度可控)。
请注意,原始数据是包含许多要解析的个体记录的 CSV 文件。连续文件中的记录(每 5 分钟大约有 1 个)之间存在明显的重叠,因此重复率很高。
简而言之,如果您有按照时间戳排序的原始数据,并且接收顺序相当,则使用内存缓存可能有助于记录去重检查。

3
很多时候,我们开发人员会遇到您描述的情况,也许会有一些变化。我想请求您将解决方案翻译为C#,以便我们和许多即将成为开发人员的人受益。+1。我很愿意看到这个解决方案扩展成博客文章! :) - sangam

3

我知道这是一个非常老的帖子,但以防像我这样的人需要这个解决方案,但在VB.NET中,这就是我根据上面的答案使用的。

Private Function ValidateUniquePayroll(PropertyToCheck As String) As Boolean
    // Return true if Username is Unique
    Dim rtnValue = False
    Dim context = New CPMModel.CPMEntities
    If (context.Employees.Any()) Then ' Check if there are "any" records in the Employee table
        Dim employee = From c In context.Employees Select c.PayrollNumber ' Select just the PayrollNumber column to work with
        For Each item As Object In employee ' Loop through each employee in the Employees entity
            If (item = PropertyToCheck) Then ' Check if PayrollNumber in current row matches PropertyToCheck
                // Found a match, throw exception and return False
                rtnValue = False
                Exit For
            Else
                // No matches, return True (Unique)
                rtnValue = True
            End If
        Next
    Else
        // The is currently no employees in the person entity so return True (Unqiue)
        rtnValue = True
    End If
    Return rtnValue
End Function

我不知道如何在VB中使用Lambda,但在C#中,这是等效的:return !context.Employees.Any(c => c.PayrollNumber == PropertyToCheck)。这样可以避免返回所有结果,然后在内存中循环。 - Colin
1
@Colin 这是一个很好的补充,我忽略了上面代码中的内存问题,在VB中,代码为context.Employees.Any(c => c.PayrollNumber <> PropertyToCheck)。我现在已经将其添加到我的代码中。 - Kevin Morrissey
Kevin,我认为你可能需要回去修复你的代码。你的逻辑肯定会在任何工资单号不匹配时返回true,而不是在没有匹配的工资单号时返回true。 - Colin
@Colin 抱歉,你是对的。我只是提供了一个VB版本的示例,因为我不太熟悉C#,所以认为“==”表示不等于,因此使用了VB中的“<>”。 - Kevin Morrissey
1
@KevinMorrissey 我认为coling是在说你需要在"context"前面加上一个“Not”。因为"return Not context.Employees.Any(c => c.PayrollNumber = PropertyToCheck)"不是(我重复一遍),_不是_和"return context.Employees.Any(c <> c.PayrollNumber = PropertyToCheck)"相同的。你明白我的意思吗?使用"return Any <>"意味着如果找到任何一个不匹配这个数字的(即使存在匹配的),无论如何都会返回true。相反,使用"Not [...].Any ="只有在找不到你要查找的行时才会返回True!你看到区别了吗? - Erx_VB.NExT.Coder
@Colin 抱歉,这两条消息都是给Kevin和Colin的。 - Erx_VB.NExT.Coder

2

我在这方面遇到了一些问题-我的EntityKey由三个属性组成(具有3个列的PK),我不想检查每个列,因为那样很丑陋。 我考虑了一个解决方案,它可以在所有实体上一直工作。

另一个原因是我不想每次都捕获UpdateExceptions。

需要一点反射来获取关键属性的值。

该代码是作为扩展实现的,以简化用法:

context.EntityExists<MyEntityType>(item);

看一下:
public static bool EntityExists<T>(this ObjectContext context, T entity)
        where T : EntityObject
    {
        object value;
        var entityKeyValues = new List<KeyValuePair<string, object>>();
        var objectSet = context.CreateObjectSet<T>().EntitySet;
        foreach (var member in objectSet.ElementType.KeyMembers)
        {
            var info = entity.GetType().GetProperty(member.Name);
            var tempValue = info.GetValue(entity, null);
            var pair = new KeyValuePair<string, object>(member.Name, tempValue);
            entityKeyValues.Add(pair);
        }
        var key = new EntityKey(objectSet.EntityContainer.Name + "." + objectSet.Name, entityKeyValues);
        if (context.TryGetObjectByKey(key, out value))
        {
            return value != null;
        }
        return false;
    }

2
我想在我的回答中添加一条评论,该回答现在已经有将近9年的历史了。我认为现在有比2010/2011年使用Entity Framework 4更清晰的解决方案和可能性。因此,我建议停止对这个答案进行负面评价,而是在下面添加一个新的/更好的答案。 - Sven
请注意,我的解决方案是通用的,适用于许多具有现有表/实体的复合键的实体,我无法更改。因此,我不是总是使用3个关键属性查询.Any(...),而是直接调用.EntityExists()。 - Sven

2

我只是检查对象是否为空,这对我来说百分之百有效

    try
    {
        var ID = Convert.ToInt32(Request.Params["ID"]);
        var Cert = (from cert in db.TblCompCertUploads where cert.CertID == ID select cert).FirstOrDefault();
        if (Cert != null)
        {
            db.TblCompCertUploads.DeleteObject(Cert);
            db.SaveChanges();
            ViewBag.Msg = "Deleted Successfully";
        }
        else
        {
            ViewBag.Msg = "Not Found !!";
        }                           
    }
    catch
    {
        ViewBag.Msg = "Something Went wrong";
    }

0

为什么不做呢?

var result= ctx.table.Where(x => x.UserName == "Value").FirstOrDefault();

if(result?.field == value)
{
  // Match!
}

这会抛出一个空引用异常,因为FirstOrDefault()如果找不到结果就会返回null。我猜你可以使用if(result?.field == value)来避免这种情况。 - ToDevAndBeyond
这样做可能会不必要地变慢,因为它会加载实体。如果你只想检查一个键是否存在,那么这样做就太浪费了。 - Douglas Gaskell

0

最佳实现方式

无论您的对象是什么,以及针对数据库中哪个表格,您唯一需要拥有的就是对象中的主键。

C# 代码

var dbValue = EntityObject.Entry(obj).GetDatabaseValues();
if (dbValue == null)
{
   Don't exist
}

VB.NET 代码

Dim dbValue = EntityObject.Entry(obj).GetDatabaseValues()
If dbValue Is Nothing Then
   Don't exist
End If

为什么有两个几乎相同的答案?区别微不足道。而且,这肯定不是最好的方法。无需从数据库中拉取所有值来检查记录是否存在,这毫无意义。 - Gert Arnold

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接