Basic rules of Cassandra data modeling recommend us to create tables per query pattern.
In practice, this generally means you will use roughly one table per
query pattern. If you need to support multiple query patterns, you
usually need more than one table.
For example, we can have this 3 tables for Users
CREATE TABLE IF NOT EXISTS users(
id uuid,
username text,
emial text,
role text,
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS users_by_role(
role text,
userid uuid,
username text,
emial text,
PRIMARY KEY (role)
);
CREATE TABLE IF NOT EXISTS users_by_email(
email text,
userid uuid,
username text,
role text,
PRIMARY KEY (email)
);
And in C# using Cassandra CSharp driver we map User to the first table:
public class User
{
public Guid Id;
public string UserName;
public string Email;
public string Role;
}
var config = new MappingConfiguration();
config.Define(new Map<User>()
.TableName("users")
.PartitionKey((o) => o.Id)
.Column((u) => u.Id, (cm) => cm.WithName("id"))
.Column((u) => u.UserName, (cm) => cm.WithName("username"))
.Column((u) => u.Email, (cm) => cm.WithName("email"))
.Column((u) => u.Role, (cm) => cm.WithName("role"))
.ExplicitColumns());
UserMapper = new Mapper(Session, config);
and read data using Mapper:
User result = UserMapper.Single<User>("WHERE id=?", guid);
My first question is: How do we map User to the other two tables users_by_role and users_by_email? I don't think that we have to create 2 more CLR types with the same properties. (Besides, we can have different columns in other tables)
And the second question: How can we utilize defined mappers in BatchStatement? For example: to insert a user to first table we'd use:
UserMapper.Insert<User>(usr);
But in our example we have to insert to 3 tables in a batch. What is the best approach?
driver version 3.1.0.1
I guess this is something that cannot be done.
You will have to proceed with mapping the 3 to each of the cassandra models.
Cassandra Table <=> c# Object.
When accessing them, based on the type of search (email, id or role), you'll need to create a level of abstraction, and this will return the user.
Related
I have a database with a table with only foreign keys. I am able to manually add data into this table, and then remove it with code. However, I am unable to add data to it. This may be because I don't actually know how to access my table, or I'm doing it wrong. Help is appreciated.
My foreign key only table:
CREATE TABLE UserCategory
(
CatID INT FOREIGN KEY REFERENCES Category(ID),
UserID NVARCHAR(128) FOREIGN KEY REFERENCES [User](ID),
CONSTRAINT PK_UserCategory PRIMARY KEY (CatID, UserID)
);
The code that links it in the model:
modelBuilder.Entity<Category>()
.HasMany(e => e.Users)
.WithMany(e => e.Categories)
.Map(m => m.ToTable("UserCategory").MapLeftKey("CatID").MapRightKey("UserID"));
As you can see, I have a table called what I created, and with the values expected.
However, when I try to access this table through code, I cannot access those values directly. I can delete values like this:
var userCats = thisUser.Categories.ToList();
foreach (var cat in userCats)
{
thisUser.Categories.Remove(cat);
}
But when I try and access the UserCategories properties, I get this:
What can I do to add new UserCategories to my database? If I just create a new Category, and add that the same way I delete one (but with Add, obviously) I am required to fill out Name. Then when I fill out Name, it tells me there's a key conflict, because it appears to be adding it to the Category table, not the UserCategory.
Here are the other two tables in question:
CREATE TABLE [User]
(
ID NVARCHAR(128) PRIMARY KEY,
FirstName VARCHAR(255) NOT NULL,
LastName VARCHAR(255),
JoinDate DATETIME,
ZipCode VARCHAR(25),
SearchRadius INT,
LoginToBusinessSide BIT
);
/*The categories businesses can fall under. */
CREATE TABLE Category
(
ID INT IDENTITY(1,1) PRIMARY KEY,
[Name] VARCHAR(255) UNIQUE NOT NULL,
);
Am I trying to access it wrong? How can I access it correctly? Thank you.
I'm not totally sure why this works, but I was able to finally save entries into my UserCategory table. This is my code:
for(var i = 0; i < categories.Length; ++i)
{
var user = db.Users.Find(thisUser.ID);
var cat = db.Categories.Find(categories[i]);
user.Categories.Add(cat);
}
I have to an active connection to both tables that the foreign keys are linked to, and then when I add one to the Categories table, it goes to the correct place.
If you understand exactly why this works, or if there's a better way, please do comment.
Let's suppose that I have a database containing Artists and their Albums.
CREATE TABLE Artist (
ArtistID INTEGER PRIMARY KEY,
ArtistName TEXT NOT NULL
);
CREATE TABLE Album(
AlbumName TEXT PRIMARY KEY,
ArtistID INTEGER REFERENCES Artist(ArtistID),
Year INTEGER
);
So then I create an application inside of Visual Studio and connect the SQLite database to my project using sqlite/sql server compact toolbox, I then want to manage the database using C#
I create an application for my users. A user wants to find all the albums by a name of an artist.
If my primary key is an autoincrement property, do I have to use syntax like :
public static IQueryable<Artist> GetPossibleArtists(string name)
{
var matches = from match in dB.Artists where match.ArtistName == name select match;
return matches;
}
public static Artist GetFirstArtistIfExists(string artistName)
{
var artistMatches = GetPossibleArtists(artistName);
if (artistMatches.Count() == 0)
return null;
return artistMatches.First();
}
So that I first access the database to find the artist by its ID because I can't simply find albums by the artist's name, because the artist's name is not a primary key and I can't search the "Albums" table by the artist's name, I can only search this table by the artist's ID
And then I can finally find all the albums by the artist's id
public static IQueryable<Album> GetPossibleAlbums(long artistID)
{
var matches = from match in dB.Albums where
match.ArtistID == artistID
select match;
return matches;
}
The questions are :
1) What am I doing wrong here and is there a better way to access all the albums of an artist so that I do not need to access the database to "find the ID of an artist by its name" first before I manage to find all the albums by the artistID?
2) I could possibly design my database wrong, are there any suggestions?
3) Is it a good idea to store the artist's name inside of the "Album" table and how do I need to do this to keep my Artist's autoincrement primary key exist at the same time?
You need a JOIN to associate the Albums table to the Artists table on the ArtistID field
var matches = from a in dB.Albums join b in db.Artists on a.ArtistID equals b.ArtistID
where b.ArtistName == artistName
select a;
join clause C# reference
Suppose that I have a table Product
CREATE TABLE Product
(
Id INT PRIMARY KEY,
Name VARCHAR(100) NOT NULL,
PersonId INT FOREIGN KEY REFERENCES Person(Id)
)
CREATE TABLE Person
(
Id INT PRIMARY KEY,
FirstName VARCHAR(100) NOT NULL,
LastName VARCHAR(100) NOT NULL,
SupervisorId INT FOREIGN KEY REFERENCES Person(Id)
)
Person is a rather vanilla example of a table that references itself.
Suppose that I want to query my Product table and populate a class with the relavant product information, as well as the ID, name, and supervisor, of every person in the chain of command regarding that product.
Suppose that I naively write the following C# (assuming I'm using AutoMapper, as well):
var dbQuery = context.Products;
var entities = dbQuery.ToArray();
var products = Mapper.Map(entities);
This will work okay only so long as the number of products is small. This is because Entity Framework will initially only query the Product table, and only fill in the Person entities as they are queried. Using something like AutoMapper here is essentially like doing SQL in a loop, and causes performance issues when the number of rows in Product becomes large.
So suppose instead, I write this:
var dbQuery = context.Products.Include(x => x.Person);
var entities = dbQuery.ToArray();
var products = Mapper.Map(entities);
Now, I'm including the first Person row associated with a row in Product, but it's still problematic because each Person has one or more supervisors to be queried.
Is there some way I can tell Entity Framework to recursively query my database so that I get the entire command-chain of people and supervisors associated with a single product so that when AutoMapper tries to map these onto my classes, no new queries are required?
Using Visual Studio and SSMS.
I have a form where a user registers a username and it's stored like this:
List<SqlParameter> sqlNewTable = new List<SqlParameter>();
sqlNewTable.Add(new SqlParameter("Username", txtUser.Text));
DAL.ExecSP("CreateUserCourses", sqlNewTable);
From there, can I create a stored procedure called CreateUserCourses in which it creates a new table where the users input (their username) is the name of a new table?
Sure you can, but why?
Supposing you have a User table and a Course table. Then just make a 3rd table which maps those tables together Called UserCourses. This is called a Many-to-Many (mapping table) and it will containing an ID of both the User, and Course and any other relevant information .
This will make your life a lot easier going forward
Many-to-many (data model)
A many-to-many relationship is a type of cardinality that refers to
the relationship between two entities1 A and B in which A may
contain a parent instance for which there are many children in B and
vice versa.
For example, think of A as Authors, and B as Books. An Author can
write several Books, and a Book can be written by several Authors
Example
student: student_id, first_name, last_name
classes: class_id, name, teacher_id
student_classes: class_id, student_id // mapping table
SQL queries could look like this
Getting all students for a class
SELECT s.student_id, last_name
FROM student_classes sc
INNER JOIN students s ON s.student_id = sc.student_id
WHERE sc.class_id = X
Getting all classes for a student
SELECT c.class_id, name
FROM student_classes sc
INNER JOIN classes c ON c.class_id = sc.class_id
WHERE sc.student_id = Y
Entity framework queries could look like this
Getting all students for a class
var students = db.Students.Where(x => x.StudentClasses
.Any(y => y.ClassId == 1);
Getting all classes for a student
var classes = db.classes.Where(x => x.StudentClasses
.Any(y => y.StudentId == 1);
We have Users, and Roles with a Many-to-Many relationship defined.
The Fluent API that defines Role:
public RoleMap()
{
// Primary Key
this.HasKey(t => t.RoleString);
// Properties
this.Property(t => t.RoleString)
.IsRequired()
.HasMaxLength(50);
// Table & Column Mappings
this.ToTable("Roles");
this.Property(t => t.RoleString).HasColumnName("Role");
// Relationships
this.HasMany(t => t.Users)
.WithMany(t => t.Roles)
.Map(m =>
{
m.ToTable("UserRoles");
m.MapLeftKey("Role");
m.MapRightKey("UserName");
});
}
the following code (as expected) wants to insert a new Role in Roles, then a matching record in UserRoles for the user "MrAdmin".
var user = db.Users.Find("MrAdmin");
user.Roles.Add(new Role("Administrator"));
db.SaveChanges();
I very specifically DO NOT want to lookup the already existing "Administrator" role from the Roles table. The actual application has an enum of Roles that get passed in, so the user does not need to be aware of the Roles table or any of its content at all.
In other words, I know the following code works, but I am looking to manipulate EF at a lower level:
var adminRole = db.Roles.Find("Administrator");
var user = db.Users.Find("MrAdmin");
user.Roles.Add(adminRole);
db.SaveChanges();
What I'm trying to do is force EF to avoid adding any -- or already existing -- records to the Roles table.
I have attempted two approaches:
1) Create an INSTEAD OF INSERT TRIGGER on the DB side, to simply accept the insert request and not let EF worry about it. But EF still knew something was fishy and threw an exception.
Trigger:
CREATE TRIGGER [dbo].[tr_DontThrowOnInsert] ON [dbo].[Roles]
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON
DECLARE #ROLE as varchar(50)
SELECT #ROLE = [Role] from inserted
IF EXISTS(SELECT [Role] FROM dbo.Roles WHERE [Role] = #ROLE)
UPDATE dbo.Roles
SET [Updated] = GetUtCDate()
output inserted.[Role]
WHERE [Role] = #Role
ELSE
INSERT INTO dbo.Roles
output inserted.[Role]
SELECT * FROM inserted
WHERE [Role] not in (SELECT [Role] FROM dbo.Roles)
END
2) override the SaveChanges() in DBContext, and mark the suspect entries as Unchanged:
public override int SaveChanges()
{
this.ChangeTracker.Entries<Role>().ForEach(r => r.State = EntityState.Unchanged);
return base.SaveChanges();
}
Exception:
Result Message: System.InvalidOperationException : Saving or accepting changes failed because more than one entity of type
'Manufacturing.Domain.LookupRole' have the same primary key value.
Ensure that explicitly set primary key values are unique. Ensure that
database-generated primary keys are configured correctly in the
database and in the Entity Framework model. Use the Entity Designer
for Database First/Model First configuration. Use the
'HasDatabaseGeneratedOption" fluent API or
'DatabaseGeneratedAttribute' for Code First configuration.
I have decided to use EF 6 Interceptors. IDbCommandTreeInterceptor and IDbCommandInterceptor
...
http://msdn.microsoft.com/en-us/data/dn469464.aspx#BuildingBlocks/