【问题标题】:Entity Framework Context 6.1.3 not Refreshed / Destroyed?实体框架上下文 6.1.3 未刷新/销毁?
【发布时间】:2017-04-11 05:51:38
【问题描述】:

在这个单元测试中,我将验证 Content byte 列的 MD5 是否被正确计算、持久化和获取。

但是,实体框架 (6.1.3) 上下文似乎没有刷新/销毁,因为在原始 SQL UPDATE 之后明显生效,但在使用新上下文获取行时未显示。

namespace UnitTests
{
    [TestClass]
    public class TestDataPacketServiceDebug
    {
        [TestInitialize]
        public void Setup()
        {
            CommonMethods.ResetDatabase();
            try
            {
                CommonMethods.ResetDataPacketDirectory();
            }
            catch (DirectoryNotFoundException)
            {
            }
        }

        [TestCategory("DataPacketService"), TestMethod]
        public void TestGetLocalFilePathDebug()
        {
            // Persist a DataPacket
            int dataPacketId;
            using (var testDBContext = new TestDBContext())
            {
                DataPacket dataPacket = new DataPacket
                {
                    Content = File.ReadAllBytes(@"Resources\SampleResources.zip"),
                    Description = "DataPacketSample consist of some random found .DLL files on disk",
                    Name = "SampleResources",
                    Version = "1"
                };
                testDBContext.DataPackets.Add(dataPacket);
                testDBContext.SaveChanges();
                dataPacketId = dataPacket.DataPacketId;
            }

            // Verify file path extraction
            using (var testDBContext = new TestDBContext())
            {
                DataPacket dataPacket = DataPacketService.GetByNameAndVersion("SampleResources", "1",
                    testDBContext);

                string extractedFilePath = DataPacketService.GetLocalFilePath(testDBContext,
                    dataPacket, "EntityFramework.dll");

                string validDestinationPath = String.Format(@"{0}\DataPackets\{1}_v{2}\EntityFramework.dll",
                    AppDomain.CurrentDomain.BaseDirectory, dataPacket.Name, dataPacket.Version);

                Assert.AreEqual(validDestinationPath, extractedFilePath);

                if (File.Exists(extractedFilePath) == false)
                {
                    Assert.Fail("SampleResources was not extracted correctly");
                }
            }
            // When setting a breakpoint here and take a look with external SQL Browser
            // (e.g. Microsoft SQL Server Management Studio), following is in order:
            // Note! Not all columns are shown
            // -----------------------------------------------------------------------------------------------
            // DataPacketId | Name            | RowVersion | Content     | MD5                      | Version
            //            1 | SampleResources | NULL       | 0x504B03... | 2zSV8IChaiyf0UfnezDHKg== | 1


            // Manually modify MD5 field in database for MD5 verification
            using (var testDBContext = new TestDBContext())
            {
                string sqlUpdate = String.Format("UPDATE dbo.DataPackets SET MD5 = 'another_MD5' WHERE DataPacketId = {0}",
                    dataPacketId);
                testDBContext.Database.ExecuteSqlCommand(sqlUpdate);
            }
            // When setting a breakpoint here we can clearly see that the row has been changed:
            // Note! Not all columns are shown
            // ----------------------------------------------------------------------------------
            // DataPacketId | Name            | RowVersion | Content     | MD5         | Version
            //            1 | SampleResources | NULL       | 0x504B03... | another_MD5 | 1

            // Verify MD5
            using (var testDBContext = new TestDBContext())
            {   
                // Fetch dataPacket with modified MD5
                DataPacket dataPacket = DataPacketService.GetByNameAndVersion("SampleResources", "1", testDBContext);

                // Verify that the raw SQL command has been successful:
                Assert.AreEqual("another_MD5", dataPacket.MD5);
                // BANG!!!!!!!!!!!!!!
                // Result Message:  Assert.AreEqual failed. Expected:< another_MD5 >.Actual:< 2zSV8IChaiyf0UfnezDHKg== >.
            }
        }
    }
}

实体:

public class DataPacket
{
    /// <summary>
    /// Identifier
    /// </summary>
    public int DataPacketId { get; set; }

    /// <summary>
    /// Concurrency Token
    /// </summary>
    public byte[] RowVersion { get; set; }

    /// <summary>
    /// Name
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// Description of data packet
    /// </summary>
    public string Description { get; set; }

    /// <summary>
    /// Version of data packet
    /// </summary>
    public string Version { get; set; }

    /// <summary>
    /// MD5 of the data packet (i.e. MD5 of Content byte array)
    /// </summary>
    public string MD5 { get; private set; }

    private byte[] content;

    /// <summary>
    /// Byte content of the data packet (i.e. 
    /// </summary>
    public byte[] Content
    {
        get { return content; }
        set
        {
            content = value;
            UpdateMD5();
        }
    }

    /// <summary>
    /// TestCase navigation DataPacket <== One-To-Many ==> TestCases
    /// </summary>
    public ICollection<TestCase> TestCases { get; set; } // DataPacket <== One-To-Many ==> TestCases

    /// <summary>
    /// Update MD5 checksum depending on content
    /// </summary>
    private void UpdateMD5()
    {
        if (content != null)
        {
            this.MD5 = GetMD5ForBytes(content);
        }
    }

    /// <summary>
    /// Get MD5 checksum for content byte array
    /// </summary>
    /// <param name="content">Content byte array</param>
    /// <returns>MD5 checksum</returns>
    public static String GetMD5ForBytes(byte[] content)
    {
        if (content != null)
        {
            System.Security.Cryptography.MD5 md5Object = System.Security.Cryptography.MD5.Create();
            return System.BitConverter.ToString(md5Object.ComputeHash(content)).Replace("-", "");
        }

        return null;
    }
}

GetByNameAndVersion

public static DataPacket GetByNameAndVersion(string name, string version, TestDBContext testDBContext)
        {
            IQueryable<DataPacket> query = testDBContext.Set<DataPacket>();
            query = query.Where(t => t.Name == name).Where(t => t.Version == version);
            return query.Single();
        }

注意!我正在使用 localDB 数据库。

【问题讨论】:

  • 我认为 EF 没有任何问题,很可能在您的 DataPacketService.GetByNameAndVersion 实现中的某个地方,例如 MD5 重新计算而不是从数据库等读取,或者例如您的测试在事务等方面存在一些问题- 没有全貌很难说。
  • 确实需要向我们展示 GetByNameAndVersion 代码。
  • 谢谢,我已经更新了问题=)
  • CommonMethods 里面是什么?

标签: c# entity-framework unit-testing


【解决方案1】:

这不是 EF 上下文问题(它按预期工作),但您的 DataPacket 类中的测试/逻辑不正确。

您有两个相关的属性,都映射到数据库表列:

/// <summary>
/// MD5 of the data packet (i.e. MD5 of Content byte array)
/// </summary>
public string MD5 { get; private set; }

private byte[] content;

/// <summary>
/// Byte content of the data packet (i.e. 
/// </summary>
public byte[] Content
{
    get { return content; }
    set
    {
        content = value;
        UpdateMD5();
    }
}

客户端 C# 代码只能设置 Content,而这又会更新 MD5 - 很好。但是当 EF 从数据库中加载实体时会发生什么?实际上,它使用相同的属性设置器(private 不是问题,因为 EF 使用反射/代码生成,因此它可以在外部调用任何类型的设置器)。

现在一切都取决于调用 setter 的顺序。在您的情况下,首先调用MD5,然后调用Content。由于您的 SQL 命令更新了MD5 列,但保持Content 不变,第一个setter 将从数据库中设置MD5 值,第二个setter 将从Content 更新回来。这当然会导致断言报告失败。

由您决定通过 SQL 更新数据库中的MD5 列是否是有效操作(基本上使MD5Content 不同步)。在 undefined 中调用属性设置器的顺序 - 目前如果您将 MD5 属性声明移到 Content 属性之后,测试将通过,但您不能依赖它。

【讨论】:

  • 这听起来很合理!谢谢你的简单解释=)试图赏金+50,但它说我必须等17小时,会这样做。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-11-11
  • 2013-12-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多