实体框架6 - DataServiceContext检测有更改

时间:2015-10-15 14:22:02

标签: c# wcf entity-framework-6 odata wcf-data-services-client

我有一个运行Entity Framework 6的WCF服务器应用程序。

我的客户端应用程序通过DataServiceContext从服务器消耗OData,在我的客户端代码中,我希望能够在上下文中调用HasChanges()方法,以查看其中的任何数据是否已更改。

我尝试使用以下扩展方法:

    public static bool HasChanges(this  DataServiceContext ctx)
    {
        // Return true if any Entities or links have changes
        return ctx.Entities.Any(ed => ed.State != EntityStates.Unchanged) || ctx.Links.Any(ld => ld.State != EntityStates.Unchanged);
    }

但它总是返回false,即使它正在跟踪的实体确实有变化。

例如,假设我有一个名为Customer的跟踪实体,则在调用SaveChanges()之前总会返回以下代码。

    Customer.Address1 = "Fred"
    if not ctx.HasChanges() then return
    ctx.UpdateObject(Customer)
    ctx.SaveChanges()

如果我注释掉 ,如果不是ctx.HasChanges()然后返回 代码行,则更改会成功保存,所以我很高兴实体已收到更改并能够保存。

似乎上传跟踪 的更改,只是因为我无法从我的代码中确定该事实。

有人能告诉我如何在DataServiceContext上确定HasChanges吗?

4 个答案:

答案 0 :(得分:3)

很远。我只是阅读了DataServiceContext.UpdateObjectInternal(entity, failIfNotUnchanged),该UpdateObject(entity)直接来自false并带有failIfNotUnchanged参数。

逻辑如下:

  • 如果已经修改,则返回; (短路)
  • 如果不变,请抛出ChangeState(); (仅来自UpdateObject
  • 否则将状态设置为已修改。 (没有发生数据检查)

所以从它的外观来看,State并不关心/检查实体的内部状态,只关注HasChanges枚举。这使得更新在没有更改时感觉有点不准确。

然而,我认为您的问题是在OP第二代码块中,您在调用UpdateObject之前检查了您的扩展程序Reference.cs 。这些实体只是美化的POCO(您可以在On-中阅读(显示隐藏文件,然后在服务参考下)。它们具有明显的属性和一些EntityDescriptor操作来通知有关更改。他们在内部做的做法是跟踪状态。事实上,有一个EntityTracker.TryGetEntityDescriptor(entity)与实体相关联,负责Customer.Address1 = "Fred"; ctx.UpdateObject(Customer); if (!ctx.HasChanges()) return; ctx.SaveChanges(); 中的状态跟踪。

底线是操作实际上非常简单,我认为你只需要使你的代码像

namespace ODataClient.ServiceReference1  // Match the namespace of the Reference.cs partial class
{
    public partial class Books  // Your entity
    {
        public bool HasChanges { get; set; } = false;  // Your new property!

        partial void OnIdChanging(int value)  // Boilerplate
        {
            if (Id.Equals(value)) return;
            HasChanges = true;
        }

        partial void OnBookNameChanging(string value)  // Boilerplate
        {
            if (BookName == null || BookName.Equals(value)) return;
            HasChanges = true;
        }
        // etc, ad nauseam
    }
    // etc, ad nauseam
}

虽然我们现在知道,但总是报告HasChanges == true,所以你也可以跳过检查。

但不要绝望!您的服务引用提供的部分类可以扩展为完全符合您的要求。它完全是样板代码,因此您可能想要编写.tt或其他一些代码。无论如何,只需将其调整为您的实体:

var book = context.Books.Where(x => x.Id == 2).SingleOrDefault();
book.BookName = "W00t!";
Console.WriteLine(book.HasChanges);

但是现在这很有效,并且同样表达了OP:

<?php
session_start();

if(!isset($_SESSION['session_level'])):
    $_SESSION['session_level'] = 0; ?>
<? endif ?>
<?php
if(isset($_POST['host'])):
    $_SESSION['host'] = $_POST['host'];
    $_SESSION['dbname'] = $_POST['dbname'];
    $_SESSION['username'] = $_POST['username'];
    $_SESSION['pw'] = $_POST['pw'];
?>
<?php endif ?>

<!DOCTYPE html>
<html>
    <head>
        <meta charset='utf-8' />
        <title>Login Test</title>
    </head>

    <body>

        <?  
        if (isset($_POST['return']))
        {
            $_SESSION['session_level'] = 0;
        }
        else if (isset($_POST['submit']))
        {   
            try
                {
                    $db = new PDO("mysql:host=".$_POST['host'].";dbname=".$_POST['dbname'], $_POST['username'], $_POST['pw']);  

                }
            catch(Exception $error)
                {
                $_SESSION['session_level'] = 0;?>
                    <a href='<?= $_SERVER['PHP_SELF'] ?>'>Click here to return.</a> 
                    <? echo "\n"; ?>
                <?die("Connection to user database failed: " . $error->getMessage());

                }
            try
                {
                $db->setAttribute(PDO::ATTR_ERRMODE, PDO:: ERRMODE_EXCEPTION);
                $query = "SHOW TABLES";
                $results = $db->query($query)->fetchAll();
                $_SESSION['session_level'] = 1;
                }
            catch(Exception $error)
                {
                    echo "Problem with query!";
                    $_SESSION['session_level'] = 0;?>
                <a href='<?= $_SERVER['PHP_SELF'] ?>'>Click here to return.</a> 
            <?  }
        }
        ?>
        <?php if($_SESSION['session_level'] == 0){?>
        <h1>Database Practice</h1>
        <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post" name='initialentry'>
            <table border='0' style='text-align: center'>
                <tr>
                    <td style='text-align: right;'>Enter host name:</td>
                    <td style='text-align: left;'>
                    <input type='text' name='host' value='localhost'>
                    </td>
                </tr>
                <tr>
                    <td style='text-align: right;'>Enter database name:</td>
                    <td style='text-align: left;'>
                    <input type='text' name='dbname' value='zxyx999'>
                    </td>
                </tr>
                <tr>
                    <td style='text-align: right;'>Enter user name:</td>
                    <td style='text-align: left;'>
                    <input type='text' name='username' value='zxyx999'>
                    </td>
                </tr>
                <tr>
                    <td style='text-align: right;'>Enter password:</td>
                    <td style='text-align: left;'>
                    <input type='password' name='pw' width='15' value='12345'>
                    </td>
                </tr>
                <tr>
                    <td style='text-align: right;'><input type="reset" name="reset" value="Reset"></td>
                    <td style='text-align: left;'><input type="submit" name="submit" value="Submit"></td>
                </tr>
            </table>
        </form>
<?php }


else if ($_SESSION['session_level'] == 1)
{
    ?>

        <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post" name='getForm'>
            <select name='select'>
                <? foreach($results as $row)
                    echo "<option value=" . $row[0] .">" .$row[0]. "</option>"; ?> 
            </select>
            <input type="submit" name="selected" value="Select">
            <input type="submit" name="return" value="Return to Main Screen">           
        </form>
    <?php   


    if(isset($_POST['selected']))
    {
        try
        {
            $db = new PDO("mysql:host=".$_SESSION['host'].";dbname=".$_SESSION['dbname'], $_SESSION['username'], $_SESSION['pw']);
        }
        catch(Exception $error)
        {
            die("Connection to user database failed: " . $error->getMessage());
        }

        try
        {
            $query = $db->prepare("SELECT * FROM " . $_POST['select']);
            $query->execute();
            $header = true;
        }
        catch(Exception $error)
        {
            echo "Query failed.";
        }
        echo "</br>";

        ?>

        <?php

        echo "<table border='1'>";

        while ($row = $query->fetch(PDO::FETCH_ASSOC))
        {
            echo "<tr>";
            if($header == 'true')
            {
                foreach($row as $index => $fieldValue)
                {
                    echo "<td>";
                    echo $index;
                    echo"</td>";
                }
            echo "</tr>";
            $header = 'false';  
            }

            echo "<tr>";
            foreach($row as $index => $fieldValue)
            {
                echo "<td>";
                echo $fieldValue;
                echo "</td>";
            }
            echo "</tr>";
        }

        echo "</table>";

    }
}

        ?>

</body>
</html>

HTH!

答案 1 :(得分:2)

您是否可能无法正确添加/编辑实体? MSDN声明您必须使用AddObjectUpdateObjectDeleteObject才能在客户端上触发更改跟踪(https://msdn.microsoft.com/en-us/library/gg602811(v=vs.110).aspx - 请参阅管理并发 )。否则你的扩展方法看起来不错。

答案 2 :(得分:0)

为了使其正常工作,必须启用自动更改跟踪。您可以在

中找到此设置
ctx.Configuration.AutoDetectChangesEnabled

上下文ctx也必须跟踪所有实体对象。 这意味着它们必须由ctx方法之一返回或明确添加到上下文中。

这也意味着它们必须由DataServiceContext的同一个实例跟踪。你是否以某种方式创建了多个上下文?

还必须正确配置模型。可能Customer.Address1未映射到数据库列。在这种情况下,EF不会检测到列的更改。

答案 3 :(得分:0)

我怀疑客户端中的datacontext不是同一个。所以更改始终是false

对于Datacontext的每次更改,您必须确保Datacontext是同一个(实例)。然后检测变化是有意义的。

另一种方法是,您必须自己跟踪更改。只需使用Trackable Entities来帮助您跟踪datacontext中实体的更改。

顺便说一句。我使用代码&#39; ctx.ChangeTracker.HasChanges()&#39;检测DataContext的变化。

    public bool IsContextDirty(DataServiceContext ctx)
    {
#if DEBUG
        var changed = ctx.ChangeTracker.Entries().Where(t => t.State != EntityState.Unchanged).ToList();
        changed.ForEach(
            (t) => Debug.WriteLine("entity Type:{0}", t.Entity.GetType()));
#endif
        return ctx != null && ctx.ChangeTracker.HasChanges();
    }