Best way to approach activity based feeds in asp.net and ravendb - asp.net-mvc

I will have an activity feed per user that displays all activity related to events the user is subscribed to and the feed will pull in the most current 20 or so activities. The way I have it setup is all activity regardless of the event its related to is stored in one collection and the document itself has an "event" property I query against and index. The basic query is just select activities from collection where event is in the users event subscription list ordered by date. I store a hash of the list of the users event subscriptions and cache the results of the query using the hash as the key for xx seconds so if another user is subscribed to same exact events I can pull the results from cache instead, I'm not concerned with results being xx seconds stale.
Edit: added model and query example
Models:
User
{
// useless properties excluded
// fixed: hashset not list, can be quite large
HashSet<string> Subscriptions { get; set; }
string SubscriptionHash { get; set; } // precomputed hash of all strings in subscriptions
}
Activity
{
// Useless properties excluded
string ActivityType { get; set; }
}
Query:
if (cache[user.SubscriptionHash] != null)
results = (HashSet<Activity>)cache[user.SubscriptionHash];
else
results = session.Query<Activity>().Where(user.Subscriptions.Contains(e => e.ActivityType)).Take(20).ToList();
// add results to cache
My concern is if this is the BEST APPROACH to handle this or if there's better ravendb voodoo to use. The single collection could grow into the millions if there's alot of activities and I could potentially be storing thousands of keys in the cache when there's thousands of users with endless combinations of subscription lists. These feeds are on the users landing page so it gets hit alot and I don't want to just throw more hardware at the problem.
So answer im really looking for is if this is the best query to use or if there's a better way to do it in Raven when I could be querying against millions of documents using list.Contains.
This is an asp.net 4.5 mvc 4 project using ravendb.

Now here is how I would approach it. This is based on RaccoonBlog PostComments
I would store each users events in a separate document (i.e. UserEvent in the example below) with the user having an additional property linking to it along with a number of events and a timestamp of the last event associated with the user. This would keep the user document much smaller but having alot of the important information
In UserEvent it would be a simple document holding id, link to the userid this document references, a "event" collection, and a lasteventid. This way each "event" becomes a sub document for maintenance if needed.
Lastly a Index on UserEvent that allows you to query the data easily
public class User
{
public string Id { get; set; }
// other user properties
public string UserEventId { get; set; }
public int NumberOfEvents { get; set; }
public DateTimeOffset LastEvent { get; set; }
}
public class UserEvent
{
public string Id { get; set; }
public string UserId { get; set; }
public int LastEventId { get; set; }
public ICollection<Event> Events { get; protected set; }
public int GenerateEventId()
{
return ++LastEventId;
}
public class Event
{
public int Id { get; set; }
public DateTimeOffset CreatedAt { get; set; }
public string ActivityType { get; set; }
// other event properties
}
}
public class UserEvents_CreationDate : AbstractIndexCreationTask<UserEvent, UserEvents_CreationDate.ReduceResult>
{
public UserEvents_CreationDate()
{
Map = userEvents => from userEvent in userEvents
from evt in userEvent.Events
select new
{
evt.CreatedAt,
EventId = evt.Id,
UserEventId = userEvent.Id,
userEvent.UserId,
evt.ActivityType
};
Store(x => x.CreatedAt, FieldStorage.Yes);
Store(x => x.EventId, FieldStorage.Yes);
Store(x => x.UserEventId, FieldStorage.Yes);
Store(x => x.UserId, FieldStorage.Yes);
Store(x => x.ActivityType, FieldStorage.Yes);
}
public class ReduceResult
{
public DateTimeOffset CreatedAt { get; set; }
public int EventId { get; set; }
public string UserEventId { get; set; }
public string UserId { get; set; }
public string ActivityType { get; set; }
}
}
public static class Helpers
{
public static DateTimeOffset AsMinutes(this DateTimeOffset self)
{
return new DateTimeOffset(self.Year, self.Month, self.Day, self.Hour, self.Minute, 0, 0, self.Offset);
}
public static IList<Tuple<UserEvents_CreationDate.ReduceResult, User>> QueryForRecentEvents(
this IDocumentSession documentSession,
Func
<IRavenQueryable<UserEvents_CreationDate.ReduceResult>, IQueryable<UserEvents_CreationDate.ReduceResult>
> processQuery)
{
IRavenQueryable<UserEvents_CreationDate.ReduceResult> query = documentSession
.Query<UserEvents_CreationDate.ReduceResult, UserEvents_CreationDate>()
.Include(comment => comment.UserEventId)
.Include(comment => comment.UserId)
.OrderByDescending(x => x.CreatedAt)
.Where(x => x.CreatedAt < DateTimeOffset.Now.AsMinutes())
.AsProjection<UserEvents_CreationDate.ReduceResult>();
List<UserEvents_CreationDate.ReduceResult> list = processQuery(query).ToList();
return (from identifier in list
let user = documentSession.Load<User>(identifier.UserId)
select Tuple.Create(identifier, user))
.ToList();
}
}
Then all you have to do to query is something like.
documentSession.QueryForRecentEvents(q => q.Where(x => x.UserId == user.Id && x.ActivityType == "asfd").Take(20)).Select(x => x.Item1);

Related

I want to get item from a foreign key inside a foreign key (if that makes any sense...)

I'm using ASP.NET Core Identity. The user ID will be as FK in the Invite model and I'm trying to display all the users that are in the invite with the desired information.
I want to display the GameName within the GameID that is assigned to the user.
So it would be something like in invite show GameName (FK in user) GameTag (user) instead of GameID with a number.
Model classes:
public class Invite
{
public int ID { get; set; }
[ForeignKey("UserId")] // ICollection<Invite> in User
[Display(Name = "Users")]
public virtual ApplicationUser User { get; set; }
}
public class ApplicationUser : IdentityUser
{
public string Description { get; set; }
[ForeignKey("GameID")]
public int? GameID { get; set; }
public string GameTag { get; set; }
public virtual ICollection<Invite> Invite { get; set; }
}
public class Game
{
public int ID { get; set; }
[Display(Name = "Game")]
public string GameName { get; set; }
public virtual ICollection<ApplicationUser> ApplicationUser { get; set; }//Allow Users to get Games FKID (Foreign key ID)
}
Getting the list of invites in the invite controller index and putting them inside viewbag for invite razor index page. it only shows GameID which is the FK inside User and I don't know how to get the information inside the Game FK from Invite that is assigned to user as FK
// GET: Invites
public async Task<IActionResult> Index()
{
ViewBag.InviteList = new List<String>();
var invite = _context.Invites;
var theuser = _context.ApplicationUser;
foreach (Invite i in invite)
{
foreach (ApplicationUser tu in theuser)
{
if (i.User.Id == tu.Id)
{
ViewBag.InviteList.Add(tu.GameID + " " +tu.GameTag);
}
}
}
return View(await _context.Invites.ToListAsync());
}
If anyone understands what I'm trying to say welcome to suggest a better title
Your code is not implemented correctly (besides the main requirement of showing GameName). Actually the info from Game is not referenced directly from ApplicationUser. Not sure why you don't include that in the class (together with GameID). Suppose you do that (including the property Game), the code can be simple like this:
var invites = await _context.Invites.AsNoTracking()
.Include(e => e.User.Game).ToListAsync();
//NOTE: this can contain duplicate strings added to `InviteList`, unless you
//include more data field in each item.
foreach (Invite i in invites)
{
ViewBag.InviteList.Add(i.User.Game.GameName + " " + i.User.GameTag);
}
If you don't want to include the property Game in the ApplicationUser, you need to join the entities, like this:
var games = await (from invite in _context.Invites
join game in _context.Games on invite.User.GameID equals game.ID
select new { game.GameName, invite.User.GameTag }).AsNoTracking().ToListAsync();
//to avoid duplicate items (as noted in the previous code)
ViewBag.InviteList = games.GroupBy(e => e.GameName)
.Select(g => g.First())
.Select(e => $"{e.GameName} {e.GameTag}").ToList();

Multiple tables update MVC .net

I am new to MVC and this is my function. There are three tables (Order, OrderNotes, Notes), ID is their primary key. One Order can have many Notes, the table OrderNotes has foreign key OrderID(from Booking table) and NotesID(from Notes table). I want to have a Order Edit page to display individual Order (FirstName, LastName), also display a list of its Notes. Here is my DB structure:
Booking table:
{ID,
FirstName,
LastName
}
BookingNotes table:
{ID,
BookingID,
NotesID
}
Notes table:
{ID,
NoteName,
StatusID
}
So how can I implement the list of Notes since it's from multiple tables? It will be able to Create New Note, Delete existing Note in the list row record, not Edit. Linq used in DB query. Thanks.
It would be a better idea to have only 2 tables:
public class Book
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
// Navigational properties
public virtual List<Note> Notes { get; set; }
}
public class Note
{
public int ID { get; set; }
public int BookID { get; set; }
public string NoteName { get; set; }
public int StatusID { get; set; }
// Navigational properties
public virtual Book Book { get; set; }
public virtual Status Status { get; set; }
}
A third table is useful when you want to reuse the same Note for a different booking. However i think this is not the case.
So to retrieve data for your context make sure you have the DbSet<Book>
public class ApplicationDbContext : DbContext
{
public virtual DbSet<Book> Bookings { get; set; }
}
In your controller (or better in a repository class):
var BookingID = 10; // this is parameter passed to the function
var myBooking = this.dbContext.Bookings
.Include(p => p.Notes)
.ThenInclude(p => p.Status)
.FirstOrDefault(p => p.ID == BookingID);
Map the retrieved booking to a ViewModel, pass it to the View and you're good to go.

Getting Error : A circular reference was detected while serializing an object of type 'Abc.Models.ProductItem'

I'm going for the edit data by ProuctId.i have 2 table like Product and productitems.so i m click on edit go for the id by getting data but that time i m fetch data by id in the product table is getting propare but after going for the list productitems data getting like this error.
this is my class ProductItmes:
[Table("ProductItems ")]
public class ProductItems
{
[Key]
public int id { get; set; }
[ForeignKey("Products")]
public int pid {get; set;}
public int Qty { get; set; }
public Decimal Rate { get; set; }
public virtual Products Products { get; set; }
}
this is my api method:
public ActionResult GetProductByid(int id)
{
var Pro = db.Product.Find(id);
var ProItemList = db.Promotion_ctc.Where(x => x.pid == Pro.id).ToList();//here i am getting list of items by productid
var Details = new
{
Pro.id,
Pro.name,
Pro.image_url,
ProItemList
};
return Json(new { data = Details });
}
idk where is my problem any one know please let me know.
When working with MVC and Entity Framework, there are cases where we make our child entities reference the parent, like you did, by declaring this property here:
public virtual Products Products { get; set; }
it's ok for entity framework, but it's not when you try to serialize this.
What's going on:
The serializer will try to serialize the parent, which has a collection of ProductItem.
The serializer tries to serialize each child.
The child has a reference to parent, so the serializer tries to serialize the parent again.
Infinite loop.
That's why people use ViewModels. Instead of just returning your entities from your action, project them into a view model, and return it. Actually, you're returning an anonymous object containing a ProItemList, which I'd guess it's a List of ProductItems. Just create a view model for it:
public class ProductItemViewModel
{
public int ItemId { get; set; }
public int ProductId {get; set;}
public int Qty { get; set; }
public Decimal Rate { get; set; }
// public virtual Products Products { get; set; } NO PRODUCT HERE
}
...then fix your action to return a List of ProductItemViewModel, instead of returning directly ProductItems, like this:
var ProItemList = db.Promotion_ctc.Where(x => x.pid == Pro.id)
.Select(i => new ProductItemViewModel
{
ItemId = i.ItemId,
ProductId = i.ProductId,
Qty = i.Qty,
Rate = i.Rate
})
.ToList();
var Details = new
{
Pro.id,
Pro.name,
Pro.image_url,
ProItemList
};
return Json(new { data = Details });
}
send only required columns to ui
like this
var passToUi = from s in listResult
select new { s.Id, s.Name };
return Json(passToUi, JsonRequestBehavior.AllowGet);

Getting all related objects via related table. Entity Framework

I have two tables. Parent table and link table that stores ids. Parent table have related data from itself. Here is Models:
public class People
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<Relation> Relations { get; set; }
}
public class Relation
{
public int Id { get; set; }
public int ParentPeopleID { get; set; }
public int ChildPeopleID { get; set; }
public People People { get; set; }
}
Some test data
And mapped them like this
HasRequired(p => p.People).WithMany(p => p.Relations).HasForeignKey(p => p.ParentPeopleID);
When I'm calling
var Peoplelist = MyDbContext.People.Include(p=>p.Relations.Select(r=>r.People)).Where(p=>p.Id==1).ToList();
It returns itself not related People. In my case it should returns People with ids: 2,3,4 but returns three People with id 1
I can get what I need by MyDbContext.Relation but need by MyDbContext.People
What i'm doing wrong?
Is there another way to do that?
Ivan is correct. You should use join keyword to join the both tables as below. You will get the desired result (People with ids: 2,3,4) by using this query:
var Peoplelist = MyDbContext.Peoples.Join(MyDbContext.Relations, p => p.Id, r => r.ChildPeopleID,
(p, r) => new {p, r})
.Where(j => j.r.ParentPeopleID == 1).ToList();
Hope this will help.

Use Entity Framework to store user specific data

I have read a few articles about .Net Entity Framework that really didn't make me want to try it out. But now I have started a small test.
I set up a MVC 3 site that will handle economy transactions for my family, just for fun. So I setup Membership provider and get the login functions working. Usually I use the Membership Guid in a column to identify each row to a specific user.
I setup this class in my project:
namespace mEconomy.Models
{
public class Transaction
{
public Guid UserID { get; set; }
public int TransactionID { get; set; }
public DateTime Date { get; set; }
public string Text { get; set; }
public string Category { get; set; }
public decimal Amount { get; set; }
}
public class TransactionDBContext : DbContext
{
public DbSet<Transaction> Transactions { get; set; }
}
}
Works fine but I get the information on all users. If user A logs on and creates a few transaction then user B can create an account and see them. What is best practice here? How do I keep the user data separated?
I even tried setting the UserID as a private like this:
private Guid UserID = (Guid)Membership.GetUser().ProviderUserKey;
But that didn't work at all.
In your controller, use a linq query or the fluent api to retrieve only the desired entries:
TransactionDBContext db = new TransactionDBContext();
Guid userID = (Guid)Membership.GetUser().ProviderUserKey;
Query builder:
var transactions = db.Transactions.Where(t => t.UserId == userID);
Or Linq:
var transactions = from transaction in db.Transactions
where transaction.UserId == userID
select transaction;
Edit:
Do you want to always get the data filtered by userId without having to do where clauses in every place?
Your best bet in this case is to create a method in the model to retrieve this data for you:
// In your model code
public IQueryable<Transaction> FromCurrentUser()
{
Guid userID = (Guid)Membership.GetUser().ProviderUserKey;
return db.Transactions.Where(t => t.UserId == userID);
}
In your "Transactions" list page, just limit the transactions by the UserId.
public ActionResult List() {
using (var db = new TransactionDBContext()) {
var results = db.Transactions.Where(x => x.UserID == (Guid)Membership.GetUser().ProviderUserKey).ToList();
return View(results);
}
}

Resources