1

I'm trying to save status messages with Entity Framework, but have the problem that every time i try to save a tweet from a user that already exists in the database, i get an exception!

{"A duplicate value cannot be inserted into a unique index. [ Table name = users,Constraint name = PK_dbo.users ]"}

I totally understand why i get this error, but have no idea how to prevent it!

To save the status messages i do the following

Version 1:

db.status.Add(status);
db.SaveChanges();

Every status message contains the complete user information as a sub-entity. I use the Model(status.cs & user.cs) of this piece of code: https://github.com/swhitley/TwitterStreamClient


With the information from this Question: Save detached entity in Entity Framework 6 I came up with this idea:

Version 2:

user usr = db.user.Where<user>(u => u.id == status.user.id).SingleOrDefault();
if (usr == null)
    db.user.Add(status.user);
else
{
    db.user.Attach(status.user);
    db.Entry(status.user).State = EntityState.Modified;
} 

db.status.Add(status);
db.SaveChanges();

But this just lead to the next error message:

Attaching an entity of type 'TwitterStreamClient.user' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.


And as requested here are my status and user classes:

user.cs:

using System;
using System.Globalization;
using System.Runtime.Serialization;
using System.ComponentModel.DataAnnotations;

namespace TwitterStreamClient
{
    [DataContract]
    public class user
    {
        //<user>
        //<id>1401881</id>
        // <name>Doug Williams</name>
        // <screen_name>dougw</screen_name>
        // <location>San Francisco, CA</location>
        // <description>Twitter API Support. Internet, greed, users, dougw and opportunities are my passions.</description>
        // <profile_image_url>http://s3.amazonaws.com/twitter_production/profile_images/59648642/avatar_normal.png</profile_image_url>
        // <url>http://www.igudo.com</url>
        // <lang>en</lang>
        // <protected>false</protected>
        // <followers_count>1027</followers_count>
        // <profile_background_color>9ae4e8</profile_background_color>
        // <profile_text_color>000000</profile_text_color>
        // <profile_link_color>0000ff</profile_link_color>
        // <profile_sidebar_fill_color>e0ff92</profile_sidebar_fill_color>
        // <profile_sidebar_border_color>87bc44</profile_sidebar_border_color>
        // <friends_count>293</friends_count>
        // <created_at>Sun Mar 18 06:42:26 +0000 2007</created_at>
        // <favourites_count>0</favourites_count>
        // <utc_offset>-18000</utc_offset>
        // <time_zone>Eastern Time (US & Canada)</time_zone>
        // <profile_background_image_url>http://s3.amazonaws.com/twitter_production/profile_background_images/2752608/twitter_bg_grass.jpg</profile_background_image_url>
        // <profile_background_tile>false</profile_background_tile>
        // <statuses_count>3390</statuses_count>
        // <notifications>false</notifications>
        // <following>false</following>
        // <verified>true</verified>
        // <contributors_enabled>false</verified>
        //</user> 

        [DataMember]
        [Key]
        public string id { get; set; }
        [DataMember]
        public string name { get; set; }
        [DataMember]
        public string screen_name { get; set; }
        [DataMember]
        public string location { get; set; }
        [DataMember]
        public string description { get; set; }
        [DataMember]
        public string profile_image_url { get; set; }
        [DataMember]
        public string url { get; set; }
        [DataMember]
        public string lang { get; set; }
        [DataMember]
        public string @protected { get; set; }
        [DataMember]
        public string followers_count { get; set; }
        [DataMember]
        public string profile_background_color { get; set; }
        [DataMember]
        public string profile_text_color { get; set; }
        [DataMember]
        public string profile_link_color { get; set; }
        [DataMember]
        public string profile_sidebar_fill_color { get; set; }
        [DataMember]
        public string profile_sidebar_border_color { get; set; }
        [DataMember]
        public string friends_count { get; set; }
        //save date only as string for now as DateTimeOffset is not supported
        //public DateTimeOffset created_at_dt { get; set; }
        [DataMember]
        public string created_at
        {
            get ; //{ return created_at_dt.ToString("ddd MMM dd HH:mm:ss zzz yyyy"); }
            set;  //{ created_at_dt = DateTimeOffset.ParseExact(value, "ddd MMM dd HH:mm:ss zzz yyyy", CultureInfo.InvariantCulture);           }
        }
        [DataMember]
        public string favourites_count { get; set; }
        [DataMember]
        public string utc_offset { get; set; }
        [DataMember]
        public string time_zone { get; set; }
        [DataMember]
        public string profile_background_image_url { get; set; }
        [DataMember]
        public string profile_background_tile { get; set; }
        [DataMember]
        public string statuses_count { get; set; }
        [DataMember]
        public string notifications { get; set; }
        [DataMember]
        public string following { get; set; }
        [DataMember]
        public string verified { get; set; }
        [DataMember]
        public string contributors_enabled { get; set; }
    }
}

status.cs

using System;
using System.Runtime.Serialization;
using System.Globalization;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace TwitterStreamClient
{
    //<status>
    //<created_at>Tue Apr 07 22:52:51 +0000 2009</created_at>
    //<id>1472669360</id>
    //<text>At least I can get your humor through tweets. RT @abdur: I don't mean this in a bad way, but genetically speaking your a cul-de-sac.</text>
    //<source><a href="http://www.tweetdeck.com/">TweetDeck</a></source>
    //<truncated>false</truncated>
    //<in_reply_to_status_id></in_reply_to_status_id>
    //<in_reply_to_user_id></in_reply_to_user_id>
    //<favorited>false</favorited>
    //<in_reply_to_screen_name></in_reply_to_screen_name>
    //<geo/>
    //<contributors/>
    //</status>

    [DataContract]
    public class status
    {
        [DataMember]
        [Key]
        public string id { get; set; }
        //save date only as string for now as DateTimeOffset is not supported
        //public DateTimeOffset created_at_dt { get; set; }
        [DataMember]
        public string created_at
        {
            get; //{ return created_at_dt.ToString("ddd MMM dd HH:mm:ss zzz yyyy"); }
            set; //{ created_at_dt = DateTimeOffset.ParseExact(value, "ddd MMM dd HH:mm:ss zzz yyyy", CultureInfo.InvariantCulture);            }
        }


        [DataMember]
        public string text { get; set; }
        [DataMember]
        public string source { get; set; }
        [DataMember]
        public string truncated { get; set; }
        [DataMember]
        public string in_reply_to_status_id { get; set; }
        [DataMember]
        public string in_reply_to_user_id { get; set; }
        [DataMember]
        public string favorited { get; set; }
        [DataMember]
        public string in_reply_to_screen_name { get; set; }
        [DataMember]
        public user user { get; set; }
        [DataMember]
        public geo geo { get; set; }
        [DataMember]
        public string contributors { get; set; }
    }

    [DataContract]
    public class geo
    {
        [Key]
        public int geoId { get; set; }
        [DataMember]
        public string type { get; set; }
        [DataMember]
        public string[] coordinates { get; set; }
    }
}

DataStore.cs

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;

namespace TwitterStreamClient
{
    class DataStore
    {
        static private TweetContext Db = null;

        private static TweetContext  getContext() {
            if(Db == null)
            {
                Db = new TweetContext();
            }
            return Db;
        }
        public static bool Add(status status)
        {
            TweetContext db = getContext();
            {
                db.status.Add(status);
                db.SaveChanges();
                return true;
            }
        }
    }
}

TweetContext.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Entity;

namespace TwitterStreamClient
{
    class TweetContext : DbContext
    {
        public DbSet<status> status { get; set; }
        public DbSet<user> user { get; set; }

        static TweetContext()
        {
            // Database initialize
            Database.SetInitializer<TweetContext>(new DbInitializer());
            using (TweetContext db = new TweetContext())
                db.Database.Initialize(false);
        }

        class DbInitializer : DropCreateDatabaseAlways<TweetContext>
        {
        }
    }
}
Community
  • 1
  • 1
Michael K.
  • 2,392
  • 4
  • 22
  • 35
  • Could you please share your setup of the status and user tables and also the current code that you are using, to be more precise what are you doing before this db.status.Add(status); db.SaveChanges(); – Louis Lewis Nov 09 '14 at 01:33
  • I've added the pieces of code you asked for! I think the main issue is, that i have to tell EF that the user needs to updated if it already exists. But when I try to use 'Attach' it tells me, that "... another entity of the same type already has the same primary key value." But as far as I can see i follow exactly the scheme decribed here: http://msdn.microsoft.com/en-us/data/jj592676.aspx (Attaching an existing but modified entity to the context) – Michael K. Nov 09 '14 at 11:47
  • Hi Miike, looks great so far, can I ask that you just please add your setup for TweetContext, especially the user and status entities. I am more interested in their configuration, so I can see primary keys and so on. – Louis Lewis Nov 09 '14 at 12:37
  • done. but also check out my solution i've found ... is there a better way? – Michael K. Nov 09 '14 at 12:47
  • great stuff, your solution seems fine to me, I don't see anything wrong with what you have done. I have 1 question, how big is the project and in how many places are you going to use that code? If it is a fair size, you might want to look at using a small repository pattern, so you could simply call a helper method on a repo perhaps called InsertOrUpdate(args). I am just not sure why your query requires the . you should simply be able to write db.user.Where(u => u.Id == status.user.id).Any() – Louis Lewis Nov 09 '14 at 13:09
  • It's just for a university excercise, but knowing how to do it right is never wrong ;) – Michael K. Nov 09 '14 at 13:15
  • and Any() is working as well i guess i just added because it was possible and it worked. ;) – Michael K. Nov 09 '14 at 13:28

2 Answers2

0

I have same issue when doing insert then where i am getting error i have reinitialize dbcontext and my issue solved i don't understand but it work fine for me

just reinitialize your dbcontext  and check ....
0

I found a solution!

In Version 2 from above I just needed to change the SingleOrDefault() to Any<user>()!

if(!db.user.Where<user>(u => u.id == status.user.id).Any<user>())
    db.user.Add(status.user);
else
{
    //db.user.Attach(status.user);
    db.Entry(status.user).State = EntityState.Modified;
}
    db.status.Add(status);
    db.SaveChanges();

I guess the problem with getting the user with SingleOrDefault() tracks the user with this ID therefore the Attach() throws an Exception because it already tracks a user with this ID. But when I use Any<user>() it just checks for existance of a user with this key and it does not track it! So Attaching the user is possible then.

Michael K.
  • 2,392
  • 4
  • 22
  • 35