I am creating a simple blogging application to get .NET MVC 4 down and I am having a problem. Everything works except for when I try to tag a blog using an array of strings for each blog like so:
public class BlogEntry
{
public List<Comment> BlogComments { get; set; }
public virtual List<String> RawTags { get; set; }
public virtual List<Tag> BlogTags { get; set; }
public virtual User Author { get; set; }
public int AuthorId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public DateTime DatePosted { get; set; }
[Key]
public int Id { get; set; }
public bool IsAcceptingComments { get; set; }
public bool IsVisible { get; set; }
public DateTime LastEdited { get; set; }
}
public class Tag
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int RefCount { get; set; }
}
Upon creating a blog and tagging it, I save tags into the BlogEntry model using this:
[HttpPost]
public int Create(string data)
{
if (data != null)
{
BlogEntry newBlog = JsonConvert.DeserializeObject<BlogEntry>(data);
newBlog.Author = Session["user"] as User;
newBlog.AuthorId = newBlog.Author.Id;
newBlog.IsVisible = true;
newBlog.IsAcceptingComments = true;
newBlog.LastEdited = DateTime.Now;
newBlog.DatePosted = DateTime.Now;
newBlog.BlogTags = new List<Tag>();
foreach (String s in newBlog.RawTags)
{
// First check to see if the tag already exists
Tag check = Db.Tags.Where(m => m.Name == s).FirstOrDefault();
if (check != null)
{
check.RefCount++;
newBlog.BlogTags.Add(check);
Db.Tags.Attach(check);
Db.Entry(check).State = System.Data.Entity.EntityState.Modified;
Db.SaveChanges();
}
else
{
// Create a new tag
Tag newTag = new Tag();
newTag.Name = s;
newTag.RefCount = 1;
newBlog.BlogTags.Add(newTag);
Db.Tags.Add(newTag);
}
}
Db.BlogEntries.Add(newBlog);
Db.SaveChanges();
return newBlog.Id;
}
return -1;
}
First I do a check to see if a tag already exists.. If it does, I try to add the same tag, check to the newBlog object. I would have thought that this would just save a reference to this Tag object in the DbSet, however, if I create multiple blogs posts with the tag "html" and then run a query to see what blogs have the html tag, only the most recently tagged blog retains this value.... What can I do so that I can have multiple BlogEntry objects with the same Tag object in the database?
I don't have my dev machine in front of me right now, so this is just a guess, but I figure it's better than making you wait until tomorrow...
I don't think you need the last 3 lines in your if(check!=null) and in fact, I wonder if they aren't messing you up:
Db.Tags.Attach(check);
Db.Entry(check).State = System.Data.Entity.EntityState.Modified;
Db.SaveChanges();
You shouldn't need to attach because you got it from the Db object already, so it should already be being tracked. This means you don't need to change the state and as for the SaveChanges, you are going to do that below.
And now another disclaimer: I've done some work with Entity Framework (version 6, if you want to know), but not with MVC, so it may be different, but my understanding is that it is better to create a new DbContext for each set of instructions, rather than having a class variable that just tracks running changes. I'm not sure if that is what you are doing or not, but it sort of looks that way from this code sample. Assuming that is relevant in MVC, you may consider creating a new DbContext (Db) at the top of your create method.
Let me know how it goes--if this doesn't help, I'll delete this answer.
First you would have to update the Tag class so that it can track its registered blog entries itself. Here the BlogEntry and Tag classes have a many-to-many relationship. So the Tag class would look like below:
public class Tag
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int RefCount { get; set; }
public virtual List<BlogEntry> BlogEntries { get; set; } // MODIFICATION
}
Now you have to add the blog entry to all of its tags for back referencing to meet your query in an easy way. Look for the modifications I have made in the for-loop below:
foreach (String s in newBlog.RawTags)
{
// First check to see if the tag already exists
Tag check = Db.Tags.Where(m => m.Name == s).FirstOrDefault();
if (check != null)
{
check.RefCount++;
check.BlogEntries.Add(newBlog); // MODIFICATION
newBlog.BlogTags.Add(check);
Db.Tags.Attach(check);
Db.Entry(check).State = System.Data.Entity.EntityState.Modified;
Db.SaveChanges();
}
else
{
// Create a new tag
Tag newTag = new Tag();
newTag.Name = s;
newTag.RefCount = 1;
newTag.BlogEntries = new List<BlogEntry>(); // MODIFICATION
newTag.BlogEntries.Add(newBlog); // MODIFICATION
newBlog.BlogTags.Add(newTag);
Db.Tags.Add(newTag);
}
}
To see what blogs have the html tag, you just have to query on the Tag class and search through the BlogEntries to get the desired blogs. Good luck!
Related
I am working with .NET MVC that have a model implement recursion as below:
public class Comment
{
[Key]
[Required]
public int CommentId { get; set; }
[Required]
public string Content { get; set; }
public bool Anonymous { get; set; }
[Required]
public DateTime Created_date { get; set; }
[Required]
public DateTime Last_modified { get; set; }
public virtual Comment Reply { get; set; }
public virtual ICollection<Comment> Replys { get; set;}
public virtual Idea Idea { get; set; }
}
In explanation, each Idea contains different Comments inside it, and each comment also has several smaller Comments that reply to the previous one. However, I have no idea how to do recursion to get replies for a comment and smaller reply for each reply until the last one in controller and also how to display them in view. Feel free to ask me any question since my explanation is not clear sometimes.
public ActionResult Index(int? i)
{
List<Idea> Ideas = db.Ideas.ToList();
foreach(Idea idea in Ideas)
{
idea.Comments = db.Comments.Include(x => x.Reply).ToList();
idea.Comments=idea.Comments.Where(x => x.Idea == idea).ToList();
foreach (Comment comment in idea.Comments)
{
comment.Replys = GetComments(comment.CommentId); //This function use to get list of reply for comment
foreach (Comment reply in comment.Replys)
{
reply.Replys=GetComments(reply.CommentId);
foreach (Comment reply2 in reply.Replys)
{
reply2.Replys=GetComments(reply2.CommentId);
foreach(Comment reply3 in reply2.Replys)
{
reply3.Replys = GetComments(reply3.CommentId);
//When would this stop ?
}
}
}
}
}
return View(Ideas.ToPagedList(i ?? 1, 5));
}
Use this loop;
foreach(Idea idea in Ideas)
{
idea.Comments = db.Comments.Include(x => x.Reply).ToList();
idea.Comments= idea.Comments.Where(x => x.Idea == idea).ToList();
foreach (Comment comment in idea.Comments)
{
RecursionGetComments(comment);
}
}
Add this function;
public void RecursionGetComments(Comment comment){
comment.Replys = GetComments(comment.CommentId);
foreach(var reply in comment.Replys){
RecursionGetComments(reply);
}
}
If the RecursionGetComments above didn't fill properly, you might need to use the ref keyword which passes the current instance of the class (pass by reference);
public void RecursionGetComments(ref Comment comment){
comment.Replys = GetComments(comment.CommentId);
foreach(var reply in comment.Replys){
RecursionGetComments(reply);
}
}
I also noticed that in your model, you are using the keyword virtual.
Virtual signals entity framework that the navigation property will be loaded automatically / lazy loading, since you are loading your comments manually, you could remove that.
This is a C# Question, using .NET framework built on Asp.NET Boilerplate.
Again, to re-emphasis the question being asked is "HOW...", so if an answer that was provided was a url link or a descriptive explanation on how something was supposed to be done, i would very much appreciate it. (Dont answer questions on how to tie shoelace by showing a picture of a tied shoe, nor do you answer "how to fish" by showing a recording of someone fishing...)
Since the question is pretty basic (i don't need to rephrase/repeat the header again), i'll give an example.
If i have a Forum service, and i create a class to load a Thread. Inside that thread class should be some sort of collection, array, list, or even a dbset of Post that is pulled on construct.
[Table("Thread", Schema = "dbo")]
public class ThreadModel
{
[Key]
public long Id { get; set; }
public string Title { get; set; }
//Idea 1
//Value should automatically be pulled and cached the moment class connects to database
public Post[] Posts { get; set; }
//Idea 2
//Post has a constructor to return all post that matches a thread id. While new tag keeps the return value constantly refreshed.
public Post[] Posts { get { return new Post(this.Id) } }
//Idea 3
//Not sure how collection is supposed to work. Does it automatically just pull or will i need to make a method to request?
public virtual ICollection<Post> Posts { get; set; }
//Example constructor
//When connected to database key-value pairs that match database labels will automatically get stored in class
protected ThreadModel()
{
//Idea 1-A
//Should be a value of null or empty if database yields no results
Posts = new Post();
}
public ThreadModel(int threadid) : this()
{
//Idea 1-A
Id = threadid;
//new Post => returns all posts in db
//Posts default value is all post in db
Posts = Posts.Select(post => post.threadid == this.id)
//Idea 3-A
Posts = Posts.Get(post => post.threadid == this.id)
//Idea 4
Posts = new Posts().GetThread(threadid);
}
}
Side questions
If all entities are created by inheriting Entity then at what point am i exposed to EntityFramework and DbContext?
I love this example here, submitted by a user as they attempt to connect ABP to their database. But their example doesn't show parent/child resources. I'm unable to find the guide they used to create that, and how it relates back to using ABP to fetch EntityFramework's DbContext example
How does this work? I'm unable to find instructions or explanation for this? (What am i to enter into google to get answers on these mechanics?)
[Table("AbpItems")]
public class Item : Entity
{
[ForeignKey("PostId")]
public Post Post { get; set; }
public int PostId { get; set; }
}
How does this integrate into/with abp's EntityFramework?
Where am i supposed to be creating my Database Table/Class? The project follows the Core.csproj, Application.csproj, and EntityFramework.csproj assembly layout. But it seems like every example is creating the classes at different stages or locations of the solution.
use GetAllIncluding. See https://github.com/aspnetboilerplate/aspnetboilerplate/issues/2617
Here's a complete solution ;
namespace EbicogluSoftware.Forum.Threads
{
[Table("Threads")]
public class Thread : FullAuditedEntity
{
[Required]
[StringLength(500)]
public virtual string Title { get; set; }
[Required]
[StringLength(2000)]
public virtual string Text { get; set; }
public virtual List<Post> Posts { get; set; }
public Thread()
{
Posts = new List<Post>();
}
}
[Table("Posts")]
public class Post : FullAuditedEntity
{
[Required]
[StringLength(2000)]
public virtual string Text { get; set; }
}
public class ThreadDto
{
public string Title { get; set; }
public string Text { get; set; }
public List<PostDto> Posts { get; set; }
public ThreadDto()
{
Posts = new List<PostDto>();
}
}
public class PostDto
{
public string Text { get; set; }
}
public class ThreadAppService : IApplicationService
{
private readonly IRepository<Thread> _threadRepository;
public ThreadAppService(IRepository<Thread> threadRepository)
{
_threadRepository = threadRepository;
}
public async Task<List<TenantListDto>> GetThreads()
{
var threads = await _threadRepository.GetAllIncluding(x => x.Posts).ToListAsync();
return threads.MapTo<List<TenantListDto>>();
}
}
}
Where am i supposed to be creating my Database Table/Class?
You can create them in YourProject.Core.proj
I'm building Backend for Mobile Application with ASP.NET MVC Framework.
I have two Objects:
public class CarLogItem : EntityData
{
public CarLogItem(): base()
{
Time = DateTime.Now;
}
public DateTime Time { get; set; }
public int RPM { get; set; }
public int Speed { get; set; }
public int RunTime { get; set; }
public int Distance { get; set; }
public int Throttle { get; set; }
[ForeignKey("Trip")]
public String Trip_id { get; set; }
// Navigation property
public TripItem Trip { get; set; }
}
and
public class TripItem : EntityData
{
public TripItem() : base()
{
UserId = User.GetUserSid();
StartTime = DateTime.Now;
logItems = new List<CarLogItem>();
}
public string UserId { get; set; }
public List<CarLogItem> logItems {get;set;}
public DateTime StartTime { get; set; }
}
and I have controller, which add new CarLogItem to database.
public class CarLogItemController : TableController<CarLogItem>
{
// POST tables/CarLogItem
public async Task<IHttpActionResult> PostCarLogItem(CarLogItem item)
{
var lastItem = db.CarLogItems.OrderByDescending(x => x.Time).FirstOrDefault();
//lastItem = (Query().Where(logitem => true).OrderBy(logitem => logitem.Time)).Last();
//checking if lastItem.Trip isn't null because
// I have entities with Trip field is null, but all of them should have it.
if (lastItem != null && lastItem.Trip != null && item.RunTime > lastItem.RunTime)
{
item.Trip = lastItem.Trip;
}
//In order to test adding of new TripItem entity to database
// I compare item.RunTime with 120, so it always true
else if (lastItem == null || item.RunTime < 120) // < lastItem.RunTime)
{
var newTrip = new TripItem();
item.Trip = newTrip;
}
else
{
throw new ArgumentException();
}
CarLogItem current = await InsertAsync(item);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
}
When I'm trying to add new CarLogItem with Trip = null it's ok, but when Trip is particular object it fails with following Exception:
The entity submitted was invalid: Validation error on property 'Id': The Id field is required
How properly to add new CarLogItem with nested TripItem?
I think that you need to populate the Id property on your TripItem, e.g.
var newTrip = new TripItem(){ Id = Guid.NewGuid() }
You need a primary key field in every entity class, like Id or CarLogItemId (ClassName + "Id"). Or just have a property with [Key] attribute:
[Key]
public string/int/Guid/any-db-supported-type MyProp { get; set; }
Entity Framework relies on every entity having a key value that it
uses for tracking entities. One of the conventions that code first
depends on is how it implies which property is the key in each of the
code first classes. That convention is to look for a property named
“Id” or one that combines the class name and “Id”, such as “BlogId”.
The property will map to a primary key column in the database.
Please see this for more details.
I also suspect this to be a problem:
public Lazy<CarLogItem> logItems { get; set; }
You don't have to mark navigation property as Lazy<>. It is already lazy (unless you have configuration that disables lazy loading). Please try to remove Lazy<> and see if it works this way.
I'm trying to optimize my EF queries and I'm stuck with this one.
Let's say I have a model like this:
public class House
{
public int ID { get; set; }
public ICollection<Window> Windows { get; set; }
}
public class Window
{
public int ID { get; set; }
public string Color { get; set; }
public WindowKind Kind { get; set; }
}
public class WindowKind
{
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
What I would like to do is to explicitly load all windows and to specify what should be populated in WindowKind property.
I know I can do it with .Include() like this:
var house = Context.Houses.Single(h => h.ID == id);
var windows = Context.Entry(house).Collection(h => h.Windows).Query().Include(w => w.Kind).Load();
However, this will create a query that will load all WindowKind properties and I need only Name, for example. I was hoping something like this would work but it does not, Windows collection is empty, although the generated query looks good.
var house = Context.Houses.Single(h => h.ID = id);
var windows = Context.Entry(house).Collection(h => h.Windows).Query().Select(w => { new w.Color, w.Kind.Name }).Load();
Is it possible to have fine grained control when loading child collections?
you can't load only a part of the scalar (int, string,...) properties of an entity by loading the entity.
In you case, something like the following should do:
from
w in Context.Windows
where
w.House.ID == id // here a navigation property is missing, but (imho) more clear for the sample
select new {
windows = w,
kName = w.Kind.Name
}
But in this case you will not get context attached entities.
I'm using MVC3 with EF4 code-first. I have the following model:
public class Order {
public int ID { get; set; }
[Required]
public float Price { get; set; }
[Required]
public int PayMethodId { get; set; }
public PayMethod PayMethod { get; set; }
public int? SpecificEventId { get; set; }
public SpecificEvent SpecificEvent { get; set; }
public int? SeasonalTicketId { get; set; }
public SeasonalTicket SeasonalTicket { get; set; }
}
When I try to save an Order object with specificEventId = 2 and specificEvent = X, a new SpecificEvent object is created in the DB, even though there's already a specific event X with ID 2 in the DB. When i try with specificEventId = 2 and specificEvent = null I get a data validation error.
What am I doing wrong? I want SpecificEvent and SeasonalTicket to be nullable, and I don't want EF4 to create a new instance of these objects in the DB whenever I save 'Order'.
Update
This is my code for saving Order in the DB:
public void SaveOrder(Order order)
{
Order fromDb = null;
// If editing an existing object.
if ((fromDb = GetOrder(order.ID)) != null)
{
db = new TicketsDbContext();
db.Entry(order).State = System.Data.EntityState.Modified;
db.SaveChanges();
}
// If adding a new object.
else
{
db.orders.Add(order);
db.SaveChanges();
}
}
When I save, I do reach the else clause.
The real question is, where did you get the instance of X from? It appears as though EF has no knowledge of this instance. You either need to fetch the already existing SpecificEvent through EF and use the proxy it returns to set your navigation property, or else tell EF to "attach" X, so that it knows what your intent is. As far as EF knows, it appears, you are trying to send it a new instance with a conflicting Id, so it is properly issuing the error.