Use Entity Framework to store user specific data - asp.net-mvc

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);
}
}

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();

Return last value of a column

I am trying to get the last value of a record from the database. I am using entity framework. I am trying to get the last value of the balance and deduct the amount user enters to get the new balance. I am new to this and trying to create a simple expense management system.
My controller
public ActionResult Create([Bind(Include = "ExpenseId,ExpenseFor,DateTime,Amount,Balance,RowVersion")] Expense expense)
{
if (ModelState.IsValid)
{
var a = db.Expenses.Select(b => b.Balance);
var c = a.Last();
expense.Balance = c - expense.Amount;
db.Expenses.Add(expense);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(expense);
}
return View(expense);
}
My model looks like this
public class Expense
{
public int ExpenseId { get; set; }
public string ExpenseFor { get; set; }
public DateTime DateTime { get; set; }
public Decimal? Amount { get; set; }
public Decimal? Balance { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
}
When I try to create new records, it says that the Method cannot be translated into a store expression. I would really appreciate any help with this.
If you use SQL server, it's no wonder .Last()function is not going to work.
There is no such things as (select last) in SQL server, so Entity basicaly fails to translate it to a database SQL server language. This is what you have to do :
var v = db.Expenses.OrderByDescending(t => t.ColumnName).First();
Or something similar, depending on what you want.
Try to think of a way to turn the query around and use First() ... or FirstOrDefault() if your are afraid of potential null values.
Your own solution :
var v = db.Expenses.OrderByDescending(t => t.ExpenseId).First();

MultiTenant Application Prevent Tenant Access Data from Other Tenant in Shared Database

I’m working on a tenant application and i was wondering how i can block tenant access other tenant data.
First, let me expose some facts:
The app is not free, 100% for sure the malicious user is a client.
All the primary keys/identity are integers (Guid solve this problem but we can't change right now).
The app use shared database and shared schema.
All the tenants are business group wich own several shops.
I'm use Forgery...
I have some remote data chosen by dropdown and its easy change the id's and acess data from other tenants, if you have a little knowledge you can f*ck other tenants data.
The first thing i think was check every remote field but this is kind annoying...
So i build a solution compatible with Code First Migrations using Model Convention and Composite Keys, few tested, working as expected.
Here's the solution:
Convention Class
public class TenantSharedDatabaseSharedSchemaConvention<T> : Convention where T : class
{
public Expression<Func<T, object>> PrimaryKey { get; private set; }
public Expression<Func<T, object>> TenantKey { get; private set; }
public TenantSharedDatabaseSharedSchemaConvention(Expression<Func<T, object>> primaryKey, Expression<Func<T, object>> tenantKey)
{
this.PrimaryKey = primaryKey;
this.TenantKey = tenantKey;
base.Types<T>().Configure(m =>
{
var indexName = string.Format("IX_{0}_{1}", "Id", "CompanyId");
m.Property(this.PrimaryKey).IsKey().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).HasColumnOrder(0).HasColumnAnnotation("Index", new IndexAnnotation(new[] {
new IndexAttribute(indexName, 0) { IsUnique = true }
}));
m.Property(this.TenantKey).IsKey().HasDatabaseGeneratedOption(DatabaseGeneratedOption.None).HasColumnOrder(1).HasColumnAnnotation("Index", new IndexAnnotation(new[] {
new IndexAttribute(indexName, 1) { IsUnique = true }
}));
});
}
}
Convetion Registration:
** On convention register i pass two properties, first the primary key and second is the tenant id.
modelBuilder.Conventions.Add(new TenantSharedDatabaseSharedSchemaConvention<BaseEntity>(m => m.Id, m => m.CompanyId));
Base Entity Model
public class BaseEntity
{
public int Id { get; set; }
public int CompanyId { get; set; }
public Company Company { get; set; }
}
Order Entity (Example)
** Here i reference the currency and client with company and all work as expected...
public class Order : BaseEntity
{
[Required]
public int CurrencyId { get; set; }
[ForeignKey("CompanyId, CurrencyId")]
public virtual Currency Currency { get; set; }
[Required]
public int ClientId { get; set; }
[ForeignKey("CompanyId, ClientId")]
public virtual Client Client { get; set; }
public string Description { get; set; }
}
Is there any impact on performance?
Is there any disadvantage compared to check every remote field?
Someone have the same idea and/or problem and came with another solution?
IMHO, anywhere in the application, you will be having a mapping that states that user x is entitled to manage or access tenant(s) a(,b). In your businesses layer you should check it the user is ever entitled to see the data using the forged ID. In your case, the forged I'd will belong to another tenant that the user does not have access to, so you will return an unauthorized / security violation exception.

The specified type member 'UsersCount' is not supported in LINQ to Entities

I have such POCO entity
public class Product : Entity
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
public virtual ICollection<Order> Orders { get; set; }
[NotMapped]
public int UsersCount
{
get
{
return Orders.Count();
}
}
}
Product access method
public IQueryable<Product> GetAll()
{
return _context.Product.Include(I=>I.Orders);
}
When I load all products into View
var model = _productService.GetAll().Select(p => new AdminProductViewModel
{
Active = p.Active,
Id = p.Id,
Name = p.Name,
UsersCount = p.UsersCount
}).ToList();
I get exception
The specified type member 'UsersCount' is not supported in LINQ to
Entities.
I really can't understand why Linq to Entity gives exception. Maybe someone explain what is wrong?
We also use calculated fields in another application like this
public class User : Entity
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Email { get; set; }
[MaxLength(50)]
public string FirstName { get; set; }
[MaxLength(50)]
public string LastName { get; set; }
public virtual ICollection<Order> Orders { get; set; }
public virtual ICollection<Statistic> Statistics { get; set; }
[NotMapped]
public bool Active
{
get
{
return Orders.Any(c => c.Active && (c.TransactionType == TransactionType.Order || c.TransactionType == TransactionType.Subscription));
}
}
[NotMapped]
public int CreditsLeft
{
get
{
return Orders.Where(w => w.Active).Sum(p => p.Credits != null ? p.Credits.Value : 0);
}
}
}
public User Get(int id)
{
return _context.User.FirstOrDefault(u => u.Id == id);
}
var user = _userService.Get(_authUser.Id);
var model = new UserViewModel
{
Active = user.Active,
FullName = user.FullName,
Email = user.Email,
};
and have no problems, EF6 don't give any exception though it also has two calculated fields User.Active and User.CreditsLeft
Keep in mind that LINQ to Entities tries to translate each LINQ statement into SQL. Your statement...
var model = _productService.GetAll().Select(p => new AdminProductViewModel...
...is a LINQ extension method (Select) against an IQueryable (_productService.GetAll()). As the documentation shows, this method takes an Expression as argument.
You can see an expression as a tree of tokens that express the task it should execute. A LINQ provider, simply said, is a dictionary of tokens in "expression language" to tokens in some other language, SQL in this case. The whole statement is translated into SQL and executed by the database. The .Net runtime only sends the statement away and processes the returned result.
Inspecting EF's source code reveals that many tokens are hard-coded: all SQL keywords, a number of built-in ("canonical") functions (like DATEDIFF) and a selection of .Net methods. Other tokens are added by mapping entity properties to database columns. Recently, ToString() was added to the .Net part of the dictionary. In EF6 we can write...
_context.Product.Select(p => p.Id.ToString())
Before that, this would raise the infamous
LINQ to Entities does not recognize the method 'System.String ToString()'
Your exception has the same cause, but it pertains to members in stead of methods. p.UsersCount is not in the dictionary because it is not mapped.
We also use calculated fields in another application like this
Here a User has been fetched from the database and materialized as a C# object. Now when you access its properties it's just .Net code running. There's no SQL translation going on here. Well... it probably triggers lazy loading (of orders and credits), but the act of accessing the property does not happen in the context of an expression.
Likewise, you can access UsersCount once you've got a Product object. If you want the database to do the heavy lifting of counting the orders, you'll have to use the expression in the LINQ statement:
var model = _productService.GetAll().Select(p => new AdminProductViewModel
{
Active = p.Active,
Id = p.Id,
Name = p.Name,
UsersCount = p.Orders.Count()
}).ToList();
Try this instead:
var model = _productService.GetAll().Select(p => new AdminProductViewModel
{
Active = p.Active,
Id = p.Id,
Name = p.Name,
UsersCount = p.Orders.Count()
}).ToList();
Linq to Entities can't translate the Product.UsersCount property into sql so it gives you the error message
The specified type member 'UsersCount' is not supported in LINQ to
Entities

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

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);

Resources