I am getting a primary key violation error when I attempt to add an item with a many-to-many relationship:
I have two classes - Articles and Tags which have a many-to-many relationship :
public class Article
{
public int ID { get; set; }
public string Text { get; set; }
public ICollection<Tag> Tags { get; set; }
}
public class Tag
{
[Key]
public string UrlSlug { get; set; }
public string Name { get; set; }
public ICollection<Article> Articles{ get; set; }
}
When I add a new Article I allow the user to input any Tags and then I want to create a new Tag if the Tag isn't created yet in the database or add the Tag to the Tags collection of the Article object if the Tag already exists.
Therefore when I am creating the new Article object I call the below function:
public static Tag GetOrLoadTag(String tagStr)
{
string tagUrl = Tag.CreateTagUrl(tagStr);
var db = new SnippetContext();
var tagFromDb = from tagdummy in db.Tags.Include(x => x.Articles)
where tagdummy.UrlSlug == tagUrl
select tagdummy;
if (tagFromDb.FirstOrDefault() != null)
{ return tagFromDb.FirstOrDefault(); }
else
{
//create and send back a new Tag
}
}
This function basically checks if there is an available Tag in the database and if so returns that Tag which is then added to the Tag collection of the Article object using article.Tags.Add().
However, when I attempt to save this using the below code I get a Violation of PRIMARY KEY constraint error
db.Entry(article).State = EntityState.Modified;
db.SaveChanges();
I can't figure out how I should go about just creating a relationship between the Article and the already existing Tag.
Use the same context instance for the whole processing of your operation and your life will be much easier:
using (var ctx = new MyContext())
{
Article article = ctx.Articles.Single(a => a.Id == articleId);
Tag tag = ctx.Tags.SingleOrDefault(t => t.UrlSlug == tagUrl);
if (tag == null)
{
tag = new Tag() { ... }
ctx.Tags.AddObject(tag);
}
article.Tags.Add(tag);
ctx.SaveChanges();
}
If you don't want to load the article from database (that query is redundant if you know that article exists) you can use:
using (var ctx = new MyContext())
{
Article article = new Article() { Id = articleId };
ctx.Articles.Attach(article);
Tag tag = ctx.Tags.SingleOrDefalut(t => t.UrlSlug == tagUrl);
if (tag == null)
{
tag = new Tag() { ... }
ctx.Tags.AddObject(tag);
}
article.Tags.Add(tag);
ctx.SaveChanges();
}
How do you go about creating new tags? And how do you attach the existing or created entity to the the article.
Use something like
Article a = new Article(...);
a.tags.add(GetOrLoadTag("some tag"));
Read this article http://thedatafarm.com/blog/data-access/inserting-many-to-many-relationships-in-ef-with-or-without-a-join-entity/
Related
TLDR:
Is it possible to:
store a document (Article) with a relation (Tags) in one Store/StoreAsync call to RavenDB, but into separate collections?
then fetch the parent document (Article) including the related documents (Tags) in one query (without including/loading the tags separately)?
Explanation
AFAIK the only way to store data to RavenDB which have a relation into separate collections is to store them individually. When you read the data, you need to Include the related documents and call Load to get them.
I wonder if there is a way to simplify this by storing and querying Articles and related Tags in one go.
I have a idea how I wish it would work (but it does not), as well a working but cumbersome example.
The examples below are split into these steps
POCOs
Storing data
Index definition
Querying the index
I put the broken-but-I-wish-it-would-work and the working examples next to each other. I think it is easier to understand it that way.
POCOs
broken-but-I-wish-it-would-work
namespace Articles
{
public class ArticlePersistance
{
public string Id { get; set; }
public string Title { get; set; }
public List<TagPersistance> Tags { get; set; } // Specify TagPersistance here
}
[DearRavenDBStoreToSeparateCollectionPlease] // Does not exist
public class TagPersistance
{
public string Id { get; set; }
public string Name { get; set; }
}
}
working
namespace Articles
{
public class ArticlePersistance
{
public string Id { get; set; }
public string Title { get; set; }
public List<string> Tags { get; set; } // Specify string here
}
public class TagPersistance
{
public string Id { get; set; }
public string Name { get; set; }
}
}
Storing data
broken-but-I-wish-it-would-work
Storing ArticlePersistance and TagPersistance into their own collections with one call to StoreAsync. AFAIK this stores the Article and Tags into the same collection.
var tag = new TagPersistance() { Name = "Tag1" };
var article = new ArticlePersistance()
{
Title = "aaa",
Tags = new List<TagPersistance> { tag } // Embed the full Tag here
};
await session.StoreAsync(article); // Only one call to StoreAsync
await session.SaveChangesAsync();
working
Storing the Article and Tags separately:
var tag = new TagPersistance() { Name = "Tag1" };
await session.StoreAsync(tag); // Store Tag separately
var article = new ArticlePersistance()
{
Title = "aaa",
Tags = new List<string> { tag.Id } // Embed only the tag id
};
await session.StoreAsync(article);
await session.SaveChangesAsync();
Index definition
broken-but-I-wish-it-would-work
Index on ArticlePersistance which stores the full Tag objects
public class Articles_Test : AbstractIndexCreationTask<ArticlePersistance>
{
public Articles_Test()
{
Map = articles =>
from article in articles
let tags = article.Tags.Select(t => LoadDocument<TagPersistance>(t)) // Load the related Tags
select new
{
Title = article.Title,
Tags = tags // Store the full Tag objects here
};
}
}
working
Index which holds only the Tag names, not the full Tag objects:
public class Articles_Test : AbstractIndexCreationTask<ArticlePersistance>
{
public Articles_Test()
{
Map = articles =>
from article in articles
let tags = article.Tags.Select(t => LoadDocument<TagPersistance>(t)) // Load the related Tags
select new
{
Title = article.Title,
Tags = tags.Select(t => t.Name) // Store only the Tag name
};
}
}
Querying the index
broken-but-I-wish-it-would-work
Finally querying the index and getting the article with the tags back.
I hoped for fetching the Article and the Tags in one go here
// This does not work
var article = await session
.Query<ArticlePersistance, Articles_Test>()
.Where(a => a.Title == "aaa")
.ToListAsync();
working
This is working, but cumbersome.
You need to care about the relation between Article and Tags which could already be specified in the Index definition.
var article = await session
.Query<ArticlePersistance, Articles_Test>()
.Where(a => a.Title == "aaa")
.Include(t => t.Tags) // Include the tags
.ToListAsync();
// Query the tags separatelly
var tags = await session.LoadAsync<TagPersistance>(article.SelectMany(a => a.Tags));
I am learning ASP.NET MVC and Entity Framework. I tried to create a gallery with photo and photo tags. I achieved what I planned somehow, but as I'm still learning, I don't know if what I coded is the best way or not, so changes or corrections would be welcome.
photo table:
photoID
ownerID
Path
imagename
tag table:
tagID
tagName
phototag table:
phototagID
tagID
photoID
This is the sample code inside my controller action which is handling photo upload:
HttpPostedFileBase myFile = Request.Files["UploadImage"];
//tags are comma seperated string posted using ajax
string tagtext = Request.Form["tags"];
// converting tags to string arrays to iterate while storing
string[] tags = tagtext.Split(',');
string OrgImgName = System.IO.Path.GetFileName(myFile.FileName);
myFile.SaveAs(Path.Combine(FilePath, myFile.FileName));
//session userid
var UserID = (int)Session["UserID"];
// Saving photo to db
photo p = new photo();
p.ownerID = UserID;
p.photopath = filepath;
p.photoname = myFile.Filename;
db.photo.add(p);
db.savechanges();
// iterating through tags to insert into database
for (int i = 0; i < tags.Length; i++)
{
var tagname = tags[i].ToString();
// checking for tag name if exists
if (!db.tags.Any(n =>n.TagName == tagname))
{
tag t = new tag();
t.TagName = tags[i].ToString();
db.tags.Add(t);
db.SaveChanges();
}
}
// getting photoid of last inserted photo of user
var photoid = (from ph in db.photos where ph.ownerID == UserID orderby ph.photoID descending select ph.photoID).FirstOrDefault();
//iterating through tags to update many to many common table
for (int i = 0; i < tags.Length; i++)
{
var tagname = tags[i].ToString();
phototag ph = new phototag();
ph.PhotoId = photoid;
ph.TagId = (from t in db.tags where t.TagName == tagname select t.TagId).FirstOrDefault();
db.phototags.Add(ph);
db.SaveChanges();
}
The above code is giving the output that I expected. Now I tested only for single user posting one photo at a time; if there is a situation where single user uploads multiple photos at a time I
think it becomes nested for loops which is not good performance-wise, and if many users upload photos at a time I don't know what the effect will be on the code and server.
To help you understand better how works the many-to-many relationship in EF, I'm going to proporse you the following model:
public class Photo
{
public Photo()
{
Tags = new HashSet<Tag>();
}
public int PhotoId { get; set; }
public int OwnerId { get; set; }
public string Path { get; set; }
public string ImageName { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
public class Tag
{
public Tag()
{
Photos = new HashSet<Photo>();
}
public int TagId { get; set; }
public string TagName { get; set; }
public virtual ICollection<Photo> Photos { get; set; }
}
With this model you don't need to have a PhotoTag class, because EF will create it for you. EF build a join table in the database with the appropriate keys of the tables it’s joining. The keys are both primary keys of the join table and foreign keys pointing to the joined tables Also, you can configure that relationship as I show below:
public class YourContext : DbContext
{
//...
public IDbSet<Photo> Photos { get; set; }
public IDbSet<Tag> Tags { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Photo>().HasMany(p => p.Tags).WithMany(c => c.Photos).Map(c=>c.ToTable("PhotoTag"));
}
}
Now, with this configuration, you only need to create first the Photo and then add the tags asociated with it:
using (var db = new YourContext())
{
//Create the photo
var photo = new Photo() { OwnerId = 1, ImageName = "Picture", Path = #"E:\Pictures" };
//Add the tags
photo.Tags.Add(new Tag() { TagName = "Family" });
photo.Tags.Add(new Tag() { TagName = "Friend" });
db.Photos.Add(photo);
db.SaveChanges();
}
You don't need to worry about fill the data in the PhotoTag table, EF will do it for you.
Update
To find if exist a tag or not, you can do this:
var tag=db.Tags.Contains(tagid);//if you have the tagid
Or also, doing this:
var tag=db.Tags.FirstOrDefault(t=>t.Name==tagName);//if you have the tagName
Now, with that tag reference, you could do something like this:
if(tag!=null)
{
photo.Tags.Add(new Tag() { TagName = tagName });
}
else
{
if(photo.Tags.All(t=>t.Id!=tag.Id))
photo.Tags.Add(tag);
}
I am using the Code First approach with my project.
Here is my object:
public class Account
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ID { get; set; }
public string name { get;set; }
}
If I create a new record it is OK:
using(var context = new context())
{
context.Accounts.add(account);
context.savechanges(); //This saves fine
}
But when I change a property it saves another record in the database:
using (var context = new context())
{
var account = context.Account.where(x => x.ID == GUID).FirstOrDefault();
if (account != null)
{
account.name = "UpdatedName";
context.savechanges(); // This creates a new record!!
}
}
I am fairly new to Entity framework; why is it creating new records each time? Is it the attribute on the ID? If it is, then how can I use GUIDS as IDs instead of integers?
The attribute in your Account option should work fine to set up the ID column as the primary key for your objects.
If you are getting a new entry added to the database when you save changes, it is almost certainly the result of you having changed the primary key (ID property) of the object after having received it from the DB. Maybe you are trying to set the GUID property yourself in some piece of code that you haven't included? (You should be letting the DB assign it).
In any case, this simple console app uses your setup and works as expected. If you don't see an obvious place in your code where you are changing the GUID, maybe you can post your actual code? (I notice a couple of typos in what is pasted in, so it doesn't appear to be what you are actually using)
public class Account
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ID { get; set; }
public string name { get; set; }
}
public class MyContext : DbContext
{
public DbSet<Account> Accounts { get; set; }
}
class Program
{
static Guid MyGuid = Guid.Empty;
static void Main(string[] args)
{
using (var context = new MyContext())
{
Account account = new Account { name = "OldName" };
context.Accounts.Add( account );
context.SaveChanges();
MyGuid = account.ID;
}
using (var context = new MyContext())
{
var account = context.Accounts.Where(x => x.ID == MyGuid).FirstOrDefault();
if (account != null)
{
account.name = "UpdatedName";
context.SaveChanges();
}
}
}
}
I have pored through StackOverflow, Google and asp.net trying to find a clear cut, basic example of how to do this. All the examples have been abstract or involved complications that do not apply. I haven't been able to extract much useful from them. So far, none of them have completely answered my question or addressed my issue(s).
I am working on an MVC project with the following model:
Article.cs:
public class Article
{
public int ArticleId { get; set; }
public string Title { get; set; }
.
.
.
public virtual ICollection<Category> Categories { get; set; }
public Article()
{
Categories = new HashSet<Category>();
}
}
Category.cs:
public class Category
{
public int CategoryId { get; set; }
public string Name { get; set; }
public virtual ICollection<Article> Articles { get; set; }
public Category()
{
Articles = new HashSet<Article>();
}
}
ArticleEntities.cs:
public class ArticleEntities : DbContext
{
public DbSet<Article> Articles { get; set; }
public DbSet<Category> Categories { get; set; }
}
An article can have many categories and a category can belong to many articles.
So far I can save/update/create all the article fields except the Categories.
I am representing them as a checkboxes in the view. I can get the values for the selected checkboxes into the controller, but, every attempt I have made to store them in the db with the article has failed.
How do I:
1) When saving an edited article, update the existing relations in the relation table without creating duplicates?
2) When saving a new article, create the chosen relations in the relation table?
I assume that you get a list of CategoryIds from the controller post action, a List<int> or more general just an IEnumerable<int>.
1) When saving an edited article, update the existing relations in the
relation table without creating duplicates?
Article article; // from post action parameters
IEnumerable<int> categoryIds; // from post action parameters
using (var ctx = new MyDbContext())
{
// Load original article from DB including its current categories
var articleInDb = ctx.Articles.Include(a => a.Categories)
.Single(a => a.ArticleId == article.ArticleId);
// Update scalar properties of the article
ctx.Entry(articleInDb).CurrentValues.SetValues(article);
// Remove categories that are not in the id list anymore
foreach (var categoryInDb in articleInDb.Categories.ToList())
{
if (!categoryIds.Contains(categoryInDb.CategoryId))
articleInDb.Categories.Remove(categoryInDb);
}
// Add categories that are not in the DB list but in id list
foreach (var categoryId in categoryIds)
{
if (!articleInDb.Categories.Any(c => c.CategoryId == categoryId))
{
var category = new Category { CategoryId = categoryId };
ctx.Categories.Attach(category); // this avoids duplicate categories
articleInDb.Categories.Add(category);
}
}
ctx.SaveChanges();
}
Note that the code also works when you have a ArticleViewModel instead of an Article, given that the property names are the same (SetValues takes an arbitrary object).
2) When saving a new article, create the chosen relations in the relation
table?
More or less the same idea as above but simpler because you don't need to compare with an original state in the database:
Article article; // from post action parameters
IEnumerable<int> categoryIds; // from post action parameters
using (var ctx = new MyDbContext())
{
foreach (var categoryId in categoryIds)
{
var category = new Category { CategoryId = categoryId };
ctx.Categories.Attach(category); // this avoids duplicate categories
article.Categories.Add(category);
// I assume here that article.Categories was empty before
}
ctx.Articles.Add(article);
ctx.SaveChanges();
}
I have replicated a stripped-down version of my code that has recently been re-written to use linq to access the database.
However, in my opinion, the linq is really simple and could probably be optimized quite a bit, especially around line 90 where there is a linq statement inside a foreach loop.
It'd be really helpful to see how someone else would go about writing this simple task using linq. Thanks in advance! Below is a snippet of my source code.
// Model objects - these are to be populated from the database,
// which has identical fields and table names.
public class Element
{
public Element()
{
Translations = new Collection<Translation>();
}
public int Id { get; set; }
public string Name { get; set; }
public Collection<Translation> Translations { get; set; }
public class Translation
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public Language Lang { get; set; }
}
}
public class Language
{
public int Id { get; set; }
public string Name { get; set; }
public string Code { get; set; }
}
// Stripped-down functions for adding and loading Element
// objects to/from the database:
public static class DatabaseLoader
{
// Add method isn't too bulky, but I'm sure it could be optimised somewhere.
public static void Add(string name, Collection<Translation> translations)
{
using (var db = DataContextFactory.Create<ElementContext>())
{
var dbElement = new Database.Element()
{
Name = name
};
db.Elements.InsertOnSubmit(dbElement);
// Must be submit so the dbElement gets it's Id set.
db.SubmitChanges();
foreach (var translation in translations)
{
db.Translations.InsertOnSubmit(
new Database.Translation()
{
FK_Element_Id = dbElement.Id,
FK_Language_Id = translation.Lang.Id,
Title = translation.Title,
Content = translation.Content
});
}
// Submit all the changes outside the loop.
db.SubmitChanges();
}
}
// This method is really bulky, and I'd like to see the many separate linq
// calls merged into one clever statement if possible (?).
public static Element Load(int id)
{
using (var db = DataContextFactory.Create<ElementContext>())
{
// Get the database object of the relavent element.
var dbElement =
(from e in db.Elements
where e.Id == id
select e).Single();
// Find all the translations for the current element.
var dbTranslations =
from t in db.Translations
where t.Fk_Element_Id == id
select t;
// This object will be used to buld the model object.
var trans = new Collection<Translation>();
foreach (var translation in dbTranslations)
{
// Build up the 'trans' variable for passing to model object.
// Here there is a linq statement for EVERY itteration of the
// foreach loop... not good (?).
var dbLanguage =
(from l in db.Languages
where l.Id == translation.FK_Language_Id
select l).Single();
trans.Add(new Translation()
{
Id = translation.Id,
Title = translation.Title,
Content = translation.Content,
Language = new Language()
{
Id = dbLanguage.Id,
Name = dbLanguage.Name,
Code = dbLanguage.Code
}
});
}
// The model object is now build up from the database (finally).
return new Element()
{
Id = id,
Name = dbElement.Name,
Translations = trans
};
}
}
}
Using some made-up constructors to oversimplify:
public static Element Load(int id)
{
using (var db = DataContextFactory.Create<ElementContext>())
{
var translations = from t in db.Translations
where t.Fk_Element_Id == id
join l in db.Languages on t.FK_Language_Id equals l.Id
select new Translation(t, l);
return new Element(db.Elements.Where(x => x.Id == id).Single(), translations);
}
}
First thing I don't like in here is all the "new Translation() { bla = bla } because they're big blocks of code, I would put them in a method where you hand them the objects and they return the new for you.
Translations.InsertOnSubmit(CreateDatabaseTranslation(dbElement, translation));
and
trans.Add(CreateTranslationWithLanguage(translation, dbLanguage));
etc, wherever you have code like this, it just muddles the readability of what you're doing in here.