EF 4.3保存一对多导致错误或重复行

时间:2012-03-14 09:19:56

标签: c# entity-framework entity-framework-4 ado.net

真的希望有人可以提供帮助,因为在过去的两个晚上我一直在梳理我的头发并且在快速的地方不去!并且拼命地试图让一个家庭项目继续进行,我正在做的事情没有任何利润,并且当我试图用它来学习一些新技术时,它正在快速耗尽我的时间。

这似乎是一个常见的问题,因为在stackoverflow上发现了一些类似的帖子,但现在似乎引导我找到了一个有效的解决方案。

我得到错误或重复行进入数据库,我的简化模型是一对多的关系。但我正在尝试将WebsiteUserSession对象存储在用户会话中,以便在每次加载页面时保存数据库命中。

型号:

public class WebsitePageTracking
{
    [Key]
    public long Id { get; set; }
    public virtual WebsiteUserSession WebsiteUserSession { get; set; }
    public DateTime DateTime { get; set; }
    public string TrackUrl { get; set; }
}

public class WebsiteUserSession
{
    [Key]
    public long Id { get; set; }
    public Guid Guid { get; set; }
    public string IpAddress { get; set; }
    public DateTime DateTime_Created { get; set; }
    public DateTime DateTime_LastSessionStart { get; set; }

    public virtual ICollection<WebsitePageTracking> WebsitePageTracking { get; set; }
}

然后我的代码如下,有点复杂,但希望得到这个想法。请注意,datacontext存储在HttpContext.Items中,这是一个建议。

/// <summary>
/// public method for fetching and persisting website user session
/// </summary>
/// <returns></returns>
public static WebsiteUserSession GetWebsiteUserSession()
{
    // set initial variables
    Guid? websiteUserGuid = null;
    string websiteUserIpAddress = HttpContext.Current.Request.UserHostAddress;
    WebsiteUserSession websiteUserSession = null;            

    // first try and get from the session
    if (HttpContext.Current.Session["WebsiteUserSession"] != null)
    {
        // load the website user session, and fetch out the guid
        websiteUserSession = HttpContext.Current.Session["WebsiteUserSession"] as WebsiteUserSession;
        websiteUserGuid = websiteUserSession.Guid;

        // check to see if ip has changed, if so drop website user session
        if (websiteUserSession.IpAddress != websiteUserIpAddress)
            websiteUserSession = null;
    }
    else
    {
        // nothing so create brand new guid
        websiteUserGuid = Guid.NewGuid();
    }

    // if we don't have one which came from the session then get the website user session using guid/ip
    if (websiteUserSession == null)
        websiteUserSession = GetWebsiteUserSession((Guid)websiteUserGuid, websiteUserIpAddress);

    // if not stored in the session add that now
    if (HttpContext.Current.Session["WebsiteUserSession"] == null)
        HttpContext.Current.Session.Add("WebsiteUserSession", websiteUserSession);

    // return back website user session object
    return websiteUserSession;
}

/// <summary>
/// gets or creates a website user session record in the database
/// </summary>
/// <param name="guid">guid generated for the website user</param>
/// <param name="ipAddress">public ip address of website user</param>
/// <returns></returns>
private static WebsiteUserSession GetWebsiteUserSession(Guid guid, string ipAddress)
{
    DataContext dataContext = HttpContext.Current.Items["DataContext"] as DataContext;

    // try and fetch a user session from database
    WebsiteUserSession websiteUserSession = dataContext
        .WebsiteUserSessions
        .Where(x => x.Guid == guid && x.IpAddress == ipAddress)
        .SingleOrDefault();

    if (websiteUserSession != null)
    {
        // if found update last session time
        websiteUserSession.DateTime_LastSessionStart = DateTime.Now;
    }
    else
    {
        // create a new website user session
        websiteUserSession = new WebsiteUserSession();
        websiteUserSession.Guid = guid;
        websiteUserSession.IpAddress = ipAddress;
        websiteUserSession.DateTime_Created = websiteUserSession.DateTime_LastSessionStart = DateTime.Now;
        dataContext.WebsiteUserSessions.Add(websiteUserSession);
    }

    // persist changes
    dataContext.SaveChanges();                

    return websiteUserSession;
}

/// <summary>
/// log a stock page view to database
/// </summary>
/// <param name="stockId">id of stock being viewed</param>
public static void LogStockPageView(int stockId)
{
    DataContext dataContext = HttpContext.Current.Items["DataContext"] as DataContext;

    WebsiteUserSession websiteUserSession = GetWebsiteUserSession();

    //dataContext.WebsiteUserSessions.Attach(websiteUserSession);
    //dataContext.Entry(websiteUserSession).State = EntityState.Unchanged;

    WebsitePageTracking pageTrack = new WebsitePageTracking();
    pageTrack.WebsiteUserSession = websiteUserSession;
    pageTrack.DateTime = DateTime.Now;
    pageTrack.TrackUrl = HttpContext.Current.Request.Url.ToString();

    dataContext.Entry(client).State = EntityState.Unchanged;
    dataContext.Entry(website).State = EntityState.Unchanged;
    dataContext.Entry(websiteUserSession).State = EntityState.Unchanged;

    //dataContext.Entry(websiteUserSession.WebsitePageTracking).State = EntityState.Detached;

    //dataContext.WebsiteUserSessions.Attach(pageTrack.WebsiteUserSession);    

    dataContext.WebsitePageTracking.Add(pageTrack);

    //dataContext.Entry(pageTrack.WebsiteUserSession).State = EntityState.Unchanged;

    dataContext.SaveChanges();
}

基本上应该发生的是有人浏览页面,调用LogStockPageView(),然后从会话,数据库或创建一个新的实体获取一个WebsiteUserSession实体。然后将WebsitePageTracking实体添加到数据库,并与WebsiteUserSession建立关联。

您可以看到尝试使其工作的所有注释行,但是我要么输入错误,要么插入大量重复的行:(

一个这样的错误是“ObjectStateManager中已存在具有相同键的对象.ObjectStateManager无法跟踪具有相同键的多个对象。”。

请有人能看到我出错的地方,甚至建议更好的解决方案吗?我真的想和EF一起努力但是很挣扎:(

谢谢,卡尔

1 个答案:

答案 0 :(得分:1)

由于EF的工作方式,这个简单的代码存在很多问题。第一个问题是使用会话。 Session将保存您的数据库查询,但您必须向应用程序添加许多特殊代码,以使其在所有预期方案中都能正常工作。我将仅在会话中存储的实体从未用于数据修改的场景中使用会话(它再也不会被EF使用)。在这种情况下,您可以使用FK属性来避免很多痛苦。

第一个问题出现在GetWebsiteUserSession中,您从该方法返回附加实体。如果您还启用了延迟加载,则可以确定在下一个请求中添加跟踪将导致ObjectDisposedException。原因是代理附加实体在内部引用当前上下文,该当前上下文位于当前请求的末尾。当实体尝试加载导航属性时,它使用该引用。如果你关闭代理创建/延迟加载它可以工作但是更好的解决方案可以在从方法返回之前分离实体,因为它将允许其余的代码只使用单个场景:

dataContext.Entry(websiteUserSession).State = EntityState.Detached;

请注意,分离会清空您的WebSitePageTracking集合。如果要分离实体图,则必须创建深度克隆(序列化/反序列化)。

第二个问题是整个LogStockPageView。如果您当前的会话实例附加到上下文,则不需要再次附加它或更改其状态,但是如果是分离的实体,则必须执行此操作。试试这个:

public static void LogStockPageView(int stockId)
{
    DataContext dataContext = HttpContext.Current.Items["DataContext"] as DataContext;

    WebsiteUserSession websiteUserSession = GetWebsiteUserSession();

    dataContext.WebsiteUserSessions.Attach(websiteUserSession);

    WebsitePageTracking pageTrack = new WebsitePageTracking();
    pageTrack.DateTime = DateTime.Now;
    pageTrack.TrackUrl = HttpContext.Current.Request.Url.ToString();

    dataContext.WebsitePageTracking.Add(pageTrack);

    // Make connection after both session and track are correctly configured and attached
    pageTrack.WebsiteUserSession = websiteUserSession;

    dataContext.SaveChanges();

    // again detach session 
    dataContext.Entry(websiteUserSession).State = EntityState.Detached;
}

您的示例代码还包含对其他实体的多个引用。那些有同样的问题。如果您将它们存储在会话中并且它们与您的保存代码有某种关联,那么您也必须处理它们。

如果您希望websiteUserSession在会话中保持整个跟踪,它将变得更加复杂,因为您不必分离,而是必须使用实体图克隆。