DbContext.ValidateEntity中的自定义验证会抛出ObjectDisposedException

时间:2014-01-07 05:51:32

标签: entity-framework

如果您能够帮助我理解为什么在从lodging.DestinationId != 0中删除检查DbContext.ValidateLodging()时会抛出ObjectDisposedException,我感谢您,如下所示。

下面给出的示例代码来自Julie的编程实体框架 - DbContext,它可以用来重现异常。

环境: Visual Studio 2012 with Entity Framework 6.0.2

已编辑7/1:包含ObjectDisposedException的StackTrace。

DbContext实施&测试数据

namespace DataAccess
{

public class BagaContext : DbContext
{
    public DbSet<Destination> Destinations { get; set; }
    public DbSet<Lodging> Lodgings { get; set; }

    protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
    {
        var result = base.ValidateEntity(entityEntry, items);

        if (result.IsValid)
        {
            ValidateLodging(result);
        }
        return result;
    }

    private void ValidateLodging(DbEntityValidationResult result)
    {
        var lodging = result.Entry.Entity as Lodging;

        // PROBLEM: Removing lodging.DestinationId != 0 causes ObjectDisposedException.
        if (lodging != null && lodging.DestinationId != 0)
        {
            if (Lodgings.Any(l => l.Name == lodging.Name &&
            l.DestinationId == lodging.DestinationId))
            {
                result.ValidationErrors.Add(
                new DbValidationError(
                "Lodging",
                "There is already a lodging named " + lodging.Name +
                " at this destination.")
                );
            }
        }
    }

    public BagaContext()
    {
        Database.SetInitializer<BagaContext>(new InitializeBagaDatabaseWithSeedData());
    }
}



public class InitializeBagaDatabaseWithSeedData : DropCreateDatabaseAlways<BagaContext>
    {
        protected override void Seed(BagaContext context)
        {
            context.Destinations.Add(new Destination
            {
                Name = "Grand Canyon",
                Lodgings = new List<Lodging>
                    {
                        new Lodging {Name = "Grand Hotel"},
                        new Lodging {Name = "Dave's Dump"}
                    }
            });
        }
    }

}

测试DbContext.ValidateEntity实施的代码

namespace Client
{
class Program
{
    static void Main(string[] args)
    {
        CreateDuplicateLodging();
        Console.ReadLine();
    }

    private static void CreateDuplicateLodging()
    {
        using (var context = new BagaContext())
        {
            var destination = context.Destinations.FirstOrDefault(d => d.Name == "Grand Canyon");
            try
            {
                context.Lodgings.Add(new Lodging
                {
                    Destination = destination,
                    Name = "Grand Hotel"
                });
                context.SaveChanges();
                Console.WriteLine("Save Successful");
            }
            catch (DbEntityValidationException ex)
            {
                Console.WriteLine("Save Failed: ");
                foreach (var error in ex.EntityValidationErrors)
                {
                    Console.WriteLine(
                    string.Join(Environment.NewLine,
                    error.ValidationErrors.Select(v => v.ErrorMessage)));
                }
            }
        }
    }
}

}

模型类

namespace Model
{
[Table("Locations", Schema = "baga")]
public class Destination
{
    public Destination()
    {
        this.Lodgings = new List<Lodging>();
    }

    [Column("LocationID")]
    public int DestinationId { get; set; }
    [Required, Column("LocationName")]
    [MaxLength(200)]
    public string Name { get; set; }
    public virtual List<Lodging> Lodgings { get; set; }
}

public class Lodging
{
    public int LodgingId { get; set; }
    [Required]
    [MaxLength(200)]
    [MinLength(10)]
    public string Name { get; set; }
    [Column("destination_id")]
    public int DestinationId { get; set; }
    public Destination Destination { get; set; }
} 

}

ObjectDisposedException的StackTrace

at System.Data.Entity.Core.Objects.ObjectContext.get_Connection()
at System.Data.Entity.Core.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption)
at System.Data.Entity.Core.Objects.ObjectQuery`1.<System.Collections.Generic.IEnumerable<T>.GetEnumerator>b__0()
at System.Lazy`1.CreateValue()
at System.Lazy`1.LazyInitValue()
at System.Lazy`1.get_Value()
at System.Data.Entity.Internal.LazyEnumerator`1.MoveNext()
at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source)
at System.Data.Entity.Core.Objects.ELinq.ObjectQueryProvider.<GetElementFunction>b__3[TResult](IEnumerable`1 sequence)
at System.Data.Entity.Core.Objects.ELinq.ObjectQueryProvider.ExecuteSingle[TResult](IEnumerable`1 query, Expression queryRoot)
at System.Data.Entity.Core.Objects.ELinq.ObjectQueryProvider.System.Linq.IQueryProvider.Execute[TResult](Expression expression)
at System.Data.Entity.Internal.Linq.DbQueryProvider.Execute[TResult](Expression expression)
at System.Linq.Queryable.Any[TSource](IQueryable`1 source, Expression`1 predicate)
at DataAccess.BagaContext.ValidateLodging(DbEntityValidationResult result) in c:\Users\User\Documents\Visual Studio 2012\Projects\Learning\EF\StackOverflowQuestions\DataAccess\BreakAwayContext.cs:line 38
at DataAccess.BagaContext.ValidateEntity(DbEntityEntry entityEntry, IDictionary`2 items) in c:\Users\User\Documents\Visual Studio 2012\Projects\Learning\EF\StackOverflowQuestions\DataAccess\BreakAwayContext.cs:line 26
at System.Data.Entity.DbContext.GetValidationErrors()
at System.Data.Entity.Internal.InternalContext.SaveChanges()
at System.Data.Entity.Internal.LazyInternalContext.SaveChanges()
at System.Data.Entity.DbContext.SaveChanges()
at Client.Program.CreateDuplicateLodging() in c:\Users\User\Documents\Visual Studio 2012\Projects\Learning\EF\StackOverflowQuestions\Client\Program.cs:line 34
at Client.Program.Main(String[] args) in c:\Users\User\Documents\Visual Studio 2012\Projects\Learning\EF\StackOverflowQuestions\Client\Program.cs:line 18
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()

感谢。

1 个答案:

答案 0 :(得分:0)

我发现了一个有点hacky的解决方案,但我不知道究竟是什么问题。幸运的是,这可能是测试引发的错误,因为您可能没有计划在测试环境之外使用DropCreateDatabaseAlways。

如果您在播种过程中没有执行任何查询,问题就会消失。一种方法是阻止验证,直到SaveChanges在app域中至少完成一次(在播种期间调用SaveChanges。)一种(可能结束)简单的方法是修改BagaContext如下:

private static bool _seedingComplete;
public override int SaveChanges()
{
   var result = base.SaveChanges();
   _seedingComplete = true;
   return result;
}

protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
   var result = base.ValidateEntity(entityEntry, items);

   if (_seedingComplete && result.IsValid)
   {
      ValidateLodging(result);
   }
   return result;
}

同样,我不确切地知道为什么在播种期间执行查询会导致此问题,但如果您能找到避免这种情况的方法,那么您应该没问题。

编辑以添加更多说明:

lodging.DestinationId != 0中if语句中的子句ValidateLodging具有防止错误的效果的原因是,在播种期间所有目标记录的ID都为0 ,因为他们尚未插入。因为在播种期间DestinationId将始终为0,所以此子句阻止执行Lodgings.Any(...)查询。

这个问题让我难以理解的一个原因是,播种期间的Lodgings.Any(...)查询不会导致抛出异常,但是 中断Lodgings DbSet,以便以后不能用于实际查询。我花了一段时间才弄清楚DbSet在哪里被破坏了。