EF 4.3 сохранение одного ко многим, вызывающее ошибки или повторяющиеся строки - PullRequest
0 голосов
/ 14 марта 2012

Действительно надеюсь, что кто-то может помочь, так как выкручивал мои волосы последние две ночи и не собирался быстро!и отчаянно пытаюсь запустить семейный проект, который я делаю без прибыли, и он быстро поглощает мое время, когда я пытаюсь использовать его для изучения некоторых новых технологий.

Кажется, это обычное делоКак я обнаружил, несколько похожих сообщений в ish на 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; }
}

Тогда мой код выглядит следующим образом, немногоболее сложный, но, надеюсь, получить идею.Обратите внимание, что текст данных хранится в 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 Ответ

1 голос
/ 14 марта 2012

У этого простого кода много проблем из-за способа работы EF.Самая первая проблема - использование сессии.Сеанс сохранит ваши запросы к базе данных, но вам придется добавить много специального кода в ваше приложение, чтобы это работало во всех ожидаемых сценариях.Я буду использовать сеанс только в сценарии, где моя сущность, сохраненная в сеансе, никогда не используется для модификации данных (она никогда больше не используется 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 сохранял весь трек в сеансе, он станет более сложным, потому что вместо отсоединения вам придется использовать клонирование графа сущностей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...