How to design a table in dynamodb for Carts - amazon-dynamodb

I have a use case where in a person can have no. of carts. This is possible because 1) user can complete an order with a cart and after that cart is considered closed 2) user can leave a cart for 2 months and it is considered expired. If user adds new item on 2 months old cart, old cart is marked expired and new cart is generated.
I tried designing following table:
Table Name: Cart
Primary Hash Key: cartId (String)
Primary Range Key: updated (String)
I am using updated as a range column so that when I query it I can get all the carts sorted on when user updated those and I can pick the first (or last) one without sorting myself to have the most recent cart. However this is messing up my use cases.
If a user adds another item, I update the item in the cart and update the updated column as well. However this creates another cart for me (with same cart id but with new updated column). After re-reading the docs, I understand that primary key is composite of cartId and updated so probably I should remove it. However I believe my use case is genuine enough and it is bad that in my case I have to do sorting in application. Another way around would be to use an auto increment as range but that is non intuitive way of putting columns. If there is a work around pls let me know. I am using DynamoDBMapper and posting my classes (with only few fields).
import java.util.Set;
import com.amazonaws.services.dynamodbv2.datamodeling.*;
#DynamoDBTable(tableName="Cart")
public class Cart {
private String cartId;
private String email;
private Set<String> cartItemsJson;
private String status;
private String created;
private String updated;
#DynamoDBHashKey(attributeName="cartId")
public String getCartId() {
return cartId;
}
public void setCartId(String cartId) {
this.cartId = cartId;
}
#DynamoDBAttribute(attributeName="email")
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
#DynamoDBAttribute(attributeName="cartItems")
public Set<String> getCartItemsJson() {
return cartItemsJson;
}
public void setCartItemsJson(Set<String> cartItemsJson) {
this.cartItemsJson = cartItemsJson;
}
#DynamoDBAttribute(attributeName="status")
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
#DynamoDBAttribute(attributeName="created")
public String getCreated() {
return created;
}
public void setCreated(String created) {
this.created = created;
}
#DynamoDBRangeKey(attributeName="updated")
#DynamoDBVersionAttribute(attributeName="updated")
public String getUpdated() {
return updated;
}
public void setUpdated(String updated) {
this.updated = updated;
}
}
This the persistence layer code. I have tried various combinations of Save behaviour but still same results.
protected static DynamoDBMapper mapper = new DynamoDBMapper(dynamoDbClient);
mapper.save(cart,new DynamoDBMapperConfig(DynamoDBMapperConfig.SaveBehavior.UPDATE));

In DynamoDB you can not update the Hash or Range key. Updating them means deleting and create a new entry.
I know that you can create a Secondary index. Maybe this will help.
Also I think you can overcome the updated as range key. You can create the table as follows:
HashKey = userId
RangeKey = cardId ( but cardId needs to be sortable for each user )
normal column = updated
normal column = etc..
When you need the last cardId of a specific user, you can get top 1 rows for a hashkey=your user and reverse sorted so you get the last one first.
When you need to add an item to the card, you don't need to update the hash/range keys.
Hope it helps.

Related

Unwanted unique constraint in many to many relationship

I'm trying to set up a Tagging tool for images. Basically I have two tables, one for pictures, and one for tags. Both are connected with a many to many setup. I can already add a single tag to a picture, and the same tag to different pictures. However, when I try to add a second tag to an image I get an exception complaining about a unique constraint that I simply don't see.
public class MediaEntity
{
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<TagEntity> Tags { get; set; }
}
public class TagEntity
{
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<MediaEntity> MediaEntities { get; set; }
}
public void updateMedia(MediaEntity model)
{
using (var db = new MediaContext(_dbLocation))
{
db.Update(model);
db.SaveChanges();
}
}
public class MediaContext : DbContext
{
private const string DB_NAME = "PT.db";
private string _path;
public DbSet<MediaEntity> MediaTable { get; set; }
public DbSet<TagEntity> TagTable { get; set; }
public MediaContext(string path)
{
_path = path;
ChangeTracker.AutoDetectChangesEnabled = false;
}
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlite($"Data Source={Path.Combine(_path, DB_NAME )}");
}
As far as I can tell my setup should create a normal many-to-many relationship, and it the database I also see pretty much this. EF automatically creates a TagTable, MediaTable, and MediaEntityTagEntityTable. But when I try to add a second tag I get this:
SqliteException: SQLite Error 19: 'UNIQUE constraint failed:
MediaEntityTagEntity.MediaEntitiesId, MediaEntityTagEntity.TagsId'.
Data from the table showing I can have the same tag on different pictures:
MediaEntitiesId
TagEntitiesId
1B48E85B-F097-4216-9B7A-0BA34E69CBFF
CF581257-F176-4CDF-BF34-09013DCEAA27
CE33F03F-5C80-492B-88C6-3C40B9BADC6C
CF581257-F176-4CDF-BF34-09013DCEAA27
523178A1-C7F8-4A69-9578-6A599C1BEBD5
0C45C9D1-7576-4C62-A495-F5EF268E9DF8
I don't see where this unique constaint comes in. How can I set up a proper many-to-many relationship?
I suspect the issue you may be running into is with the detached Media and associated Tags you are sending in. You are telling EF to apply an 'Update' to the media, but the DbContext will have no idea about the state of the Tags attached. Assuming some tags may have been newly attached, others are existing relationships. If the Context isn't tracking any of these Tags, it would treat them all as inserts, resulting in index violations (many to many) or duplicate data (many to one / one to many)
When dealing with associations like this, it is generally simpler to define more atomic actions like: AddTag(mediaId, tagId) and RemoveTag(mediaId, tagId)
If you are applying tag changes along with potential media field updates in a single operation I would recommend rather than passing entire entity graphs back and forth, to use a viewModel/DTO for the tag containing a collection of TagIds, from that apply your tag changes against the media server side after determining which tags have been added and removed.
I.e.:
public void updateMedia(MediaViewModel model)
{
using (var db = new MediaContext(_dbLocation))
{
var media = db.Medias.Include(x => x.Tags).Single(x => x.MediaId = model.MedialId);
// Ideally have a Timestamp/row version number to check...
if (media.RowVersion != model.RowVersion)
throw new StaleDataException("The media has been modified since the data was retrieved.");
// copy media fields across...
media.Name = model.Name;
// ... etc.
var existingTagIds = media.Tags
.Select(x => x.TagId)
.ToList();
var tagIdsToRemove = existingTagIds
.Except(model.TagIds)
.ToList();
var tagIdsToAdd = model.TagIds
.Except(existingTagIds)
.ToList();
if(tagIdsToRemove.Any())
media.Tags.RemoveRange(media.Tags.Where(x => tagIdsToRemove.Contains(x.TagId));
if(tagIdsToAdd.Any())
{
var tagsToAdd = db.Tags.Where(x => tagIdsToAdd.Contains(x.TagId)).ToList();
media.Tags.AddRange(tagsToAdd);
}
db.SaveChanges();
}
}
Using this approach the DbContext is never left guessing about the state of the media and associated tags. It helps guard against stale data overwrites and unintentional data tampering (if receiving data from web browsers or other unverifiable sources), and by using view models with the minimum required data, you improve performance by minimzing the amount of data sent over the wire and traps like lazy load hits by serializers.
I always explicitly create the join table. The Primary Key is the combination of the two 1:M FK attributes. I know EF is supposed to map automatically, but since it isn't, you can specify the structure you know you need.

Data always filled by null in dbcontext in .net core 3.1 api

Dears, I am using a dbfirst API . net core 3.1.
I created a migration file to apply stored procedure, and it is successfully created.
protected override void Up(MigrationBuilder migrationBuilder)
{
string procedure1 = #"CREATE PROCEDURE [dbo].[Spz_order] #client varchar(10) as
SELECT .....
having ... =#client";
migrationBuilder.Sql(procedure1);
}
Then I created a model or class to receive the data in it
[NotMapped]
public partial class categoryStock
{
public string model { get; }
public int quantity { get; }
}
Then a dbset
public virtual DbSet<categoryStock> Categories { get;}
Then in controller
[HttpGet("getCategoryStock")]
public async Task<List<categoryStock>> getCategoryStock(string cat)
{
using (_context)
{
if (_context.Categories != null)
{
var category =await _context.Categories
.FromSqlRaw("EXECUTE Spz_order {0}", cat)
.ToListAsync();
return category.ToList();
}
return new List<categoryStock>();
}
}
BUT i always get data by null, can any one help me to solve this issue
Thanks in advance
First, please check your database, make sure the data table contains records.
Second, please check your query statement in the stored procedure, make sure the query result is not null.
Third, please check the parameter value, make sure it is correct and you can find records according to the value.
Edit:
Perhaps the issue is related to the [NotMapped] attribute. The NotMapped attribute is used to specify that an entity or property is not to be mapped to a table or column in the database.
According to your code, I have created a model without using the NotMapped attribute, then, I can call the stored procedure and get the data.
Code as below:
public IActionResult Index()
{
var title = "G";
var param = new SqlParameter("#Title", "Rio");
//var movies = context.Movie
// .FromSqlRaw("GetMovies #Title", param)
// .ToList();
var movies = context.Movie
.FromSqlRaw("EXECUTE GetMovies {0}", title)
.ToList();
return View();
}
The screenshot:
So, try to remove the [NotMapped] attribute.

Update database items from the website

My current problem is (probably) not necessarily directly related to MVC 6, but how working with database actually works, and therefore any help/suggestions in this matter would be more than appreciated.
For the sake of this question, let's say that we have a very simple database with the following tables (C# classes) [we are using Entity Framework to work with the database]:
public class ShoppingUser
{
public int Id { get; set; }
public string UserName { get; set; }
public ICollection<ShoppingItem> Items { get; set; }
}
public class ShoppingItem
{
public int Id { get; set; }
public string Quantity { get; set; }
public string Text { get; set; }
public bool ToRemove { get; set; }//if item has been bought, it can be removed from the shopping list
}
This demo will be for a super duper simple shopping list app, where user (ShoppingUser who is registered in the system can have a List of ShoppingItem where user can decide on what is the text of the item (e.g. Bread, Butter, Tomatoes, ...) and also a quantity (3 pieces, 5kg, ... simple string)
Afterwards in my ASP.NET Core app, I have defined a repository which is communicating with the database and has access to the ShoppingItem class (as we are only interested in shopping items of currently logged in user).
Example of some method we could use here:
public IEnumerable<ShoppingItem> ReturnUserItems(string sUsername)
{
if (string.IsNullOrWhiteSpace(sUsername))
return null;
var result = _context.ShoppingUsers.Include(n => n.Items).Where(n => n.UserName == sUsername).FirstOrDefault();
if (result != null)
return result.Items;
else
return null;
}
Finally we have an API controller with JsonResult for either GET, POST, DELETE, ..., which is used for communication between client side AngularJs App and our server side logic.
Example of GET Method:
// GET: /<controller>/
[HttpGet("")]
public JsonResult Get(string sUserName)
{
try
{
var results = _repository.ReturnUserItems(User.Identity.Name);
if (results != null)
{
var result = Mapper.Map<IEnumerable<ShoppingItemViewModel>>(results);
return Json(result);
}
Response.StatusCode = (int)HttpStatusCode.OK;
}
catch (Exception ex)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(new { Message = ex.Message });
}
return null;
}
Here comes the tricky part (at least for me). From video tutorials I have learned, that I should never (or almost never) expose my real database model to the website (I guess it's for security reasons). Due to that (as visible from my GET method above) I have declared my ShoppingItemViewModel which contains only properties I want to expose to the user (e.g. meaning that Id of my item is not visible).
This is how it looks like:
public class ShoppingItemViewModel
{
public string Quantity { get; set; }
[Required]
public string Text { get; set; }
[Required]
public bool ToRemove { get; set; }//if item has been bought, it can be removed from the shopping list
}
And for the communication from my AngularJS App I am using simple $http.get and $http.post calls for retrieving / posting updated data.
Finally the question:
My problem is, that if a user decides to either delete an item from his shopping list, or decides to change the content of either text / quantity (meaning that originally in the database it was tomatoes - 5 kg but he manages to buy only 2 kg and therefore changes the quantity to tomatoes - 3kg), how can the app understand which elements have actually been changed and how? The problem I have in this case is, that we are no longer exposing the database Id of the items.
If I was writing a desktop app, where I wouldn't have to create this sub view (ShoppingItemViewModel), my EntityFramework is intelligent enough to check & update all the changes in my database. Unfortunately in this case, I do not understand how this is achievable.
When I was thinking about it I came with the following: Add a new property into the ShoppingItem and ShoppingItemViewModel: public string sCustomKey {get; set; }, which would serve as a unique key for every item. This way, we no longer need to expose our database Id, but we are exposing the 'fake' one.
Second question:
I case my solution would be accurate, what is the best way to update items in the database? The only way I can think of is iterating through all the items in the database and manually check for changes?
Example of what I have in mind:
//IEnumerable<ShoppingItem> would be re-mapped result of ShoppingItemViewModel we have received back from the website
public void UpdateValues(IEnumerable<ShoppingItem> items, string sUserName)
{
//retrieves list of shopping items for specified customer
var allItems = _context.ShoppingUsers
.Include(n => n.Items)
.FirstOrDefault(n => n.UserName == sUserName);
//updates the values
foreach (var sItem in items)
{
var updatedItem = allItems.Items.FirstOrDefault(n => n.Text == sItem.sCustomKey);
if (updatedItem == null)
{
//create new item
var newItem = new ShoppingItem();
newItem.Text = sItem.Text;
newItem.ToRemove = sItem.ToRemove;
allItems.Items.Add(newItem);
}
else
updatedItem.ToRemove = sItem.ToRemove;
}
_context.SaveChanges();
}
But this approach does not seem right to me.
Any help regarding these matters would be more than appreciated as I am still learning how to work with ASP.NET Core and web projects.
In your first question, exposing the item ID in the ViewModels is fine. In your domain layer, you can add validation logic that those ID exists/valid item.
Alternatively, you can use a Guid for your item/product because the ID (int) can easily be predicted.
As far as updating the items, you should not use the "username" as Identifier (of the cart) because that can be predicted/altered by the calling client. You can use Guid either persisted(to Db) or
in-memory. You can add validation as well if this Guid belongs to this username/emailAddress. So updating the items in the cart, consider adding/removing one at a time if that is doable
instead of sending list of items.
I think you have misunderstood something.
Here comes the tricky part (at least for me). From video tutorials I have learned, that I should never (or almost never) expose my real database model to the website (I guess it's for security reasons). Due to that (as visible from my GET method above) I have declared my ShoppingItemViewModel which contains only properties I want to expose to the user (e.g. meaning that Id of my item is not visible).
ViewModel <=> Domain Model <=> ReadModel (Database Model)
The point is that you shouldn't use your ReadModel(Database model) as your ViewModel in Presentation Layer (MVC). All three models will have identity.

Objectify How to Assign value to Parent Key

I'm getting my feet wet with persistence and Objectify. I'd like some guidance on assigning a Parent key. My specific questions are in all caps. Thanks.
(The sample model below contains an AppUser and a Video. The idea is like YouTube; a user creates videos that belong to him/her.)
#Entity
class Video{
// QUESTION 1: SHOULD THIS CLASS HAVE ONLY 1 KEY FIELD IF I WANT A
PARENT RELATIONSHIP WITH AppUser, AND TYPE IS Key<AppUser> ?
#Parent Key<AppUser> owner;
#Id private Long id;
protected Video(){}
protected Video(User u){ // GAE User object
AppUser au = ofy().load().type(AppUser.class).filter("userId",u.getUserId()).first().get();
// QUESTION 2: WHICH WAY IS RIGHT (TO ASSIGN PARENT KEY)?
this.owner = Key.create(au.getKey(),AppUser.class,au.getId());
// or:
// owner = au.getKey();
// or:
// owner = au;
}
}
#Entity
public class AppUser {
#Id private String userId;
// QUESTION 3: DO ALL CLASSES REQUIRE A KEY FIELD?
private Key<AppUser> key;
protected AppUser(){}
protected AppUser(User u){// GAE User object
this.userId = u.getUserId();
}
public String getId(){
return userId;
}
public Key<AppUser> getKey(){
// QUESTION 4: IS THIS THE CORRECT WAY TO RETURN THE KEY?
// WOULD THAT IMPLY I NEED TO EXPLICITLY ASSIGN A VALUE TO FIELD key?
return this.key;
// THE ALTERNATIVE WOULD BE TO CREATE A KEY
AND RETURN IT RIGHT? (THEN I CAN EXCLUDE FIELD key?)
// return Key.create(AppUser.class, userId);
}
}
Answering my own question based on further knowledge:
Normally if a parent relationship is desired, one Key is fine. I can't see why another Key field would be required.
I don't think there's 1 right way to assign a value to the #Parent Key. Using this seems to work:
this.parent = Key.create(instanceOfParent);
All classes do not REQUIRE a key field. Use when needed.
There's no one right way to return a Key, both examples could work.

problem with asp.net gridview

I have problem with gridview deleting.I have table name Doctor with
Id,Name,Address,Phone.Id is auto generated field.After adding data
when i am displaying in gridview then if delete any id from gridview
Again then if i add any new details from the form its starting from
the new number.I mean if i delete the last id no 5 then again if i
add any new doctor its taking id value 6 not from 5.My query is it
should start again from 5.Here is my code.Pls help me.
public class Doctor
{
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
}
public static class DoctorDataLayer
{
public static void AddDoctor(Doctor doctor)
{
string connectionString = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString; // JohannesH: Changed from .ToString() to .ConnectionString
using(var connection = new SqlConnection(connectionString))
{
using (var command = new SqlCommand("insert into doctor values(#name,#address,#phone)", connection))
{
command.Parameters.AddWithValue("#name", doctor.Name);
command.Parameters.AddWithValue("#address", doctor.Address);
command.Parameters.AddWithValue("#phone", doctor.Phone);
connection.Open();
command.ExecuteNonQuery();
connection.Close();
}
}
}
}
public static class DoctorBusinessLayer
{
public static void CreateDoctor(string name, string address, string phone)
{
DoctorDataLayer.AddDoctor(new Doctor {Name = name, Address = address, Phone = phone});
}
}
This is perfectly normal database behaviour and has nothing to do with your GridView. If you have an issue with gaps in autogenerated (identity) columns, either use your own logic to generate unique ID's or use custom SQL scripts to check for gaps in Identity values and fill those gaps.
Example B in the Transact-SQL reference shows a way to do just this.
So the Id is created by the database (autonumber). When id 5 is used it's used up. This is normal behavior.
As other have noted, if this is an autogenerated ID from the DB then once it is used it will not be regenerated, each ID is unique regardless if the data still exists or not. If IDs were recycled you could get into issues with foreign references that may have pointed to the old item with that ID and now would point to a new different record with the reused ID.
Typically you don't expose the IDs to the user anyway so it is a non issue.
You shouldn't depend on autogenerated ids sequences being ordered or not having gaps. As others have noted, the behavior you are seeing is perfectly normal behavior for an autogenerated id and to make it otherwise you'll need to jump through a lot of hoops. If you need the ids to be ordered by the insertion sequence, you should put in an autogenerated date/time field and then select the data ordered by that field (and index it). That way if you ever decide to switch from a numeric id to a GUID or some other id format in which the sort order is different than the insertion order your data will still be ordered correctly. If you need to have a "place order" for each, generate that automatically (say a rownumber) as you are selecting ordered by date. That way you will still have strict numerical ordering even if records get deleted later.

Resources