Adding attachments to task records in T_SQL - asp.net

I have an application which stores tasks and I want to add attachments to those tasks.
I have tried three different ways of doing this and don't know if any of them are correct and am looking for advice on where to go:
For example, simplified I have used a table:
+----------------------------------------------------------------------------+
| TaskID Description attachmentString |
+----------------------------------------------------------------------------+
| 1 Task1 "FileName1:::fileLocation;FileName2:::fileLocation" |
| 2 Task2 "FileName3:::fileLocation;FileName4:::fileLocation" |
+----------------------------------------------------------------------------+
This is similar to how profile data is stored in ASP.NET membership.
I have also tried:
+---------------------------+
| TaskID Description |
+---------------------------+
| 1 Task1 |
| 2 Task2 |
+---------------------------+
+------------------------------------------------------+
| AttachmentId Description Location TaskId |
+------------------------------------------------------+
| 1 FileName1 FileLocation 1 |
| 2 FileName2 FileLocation 1 |
+------------------------------------------------------+
If I use the first option, I can just select tasks and get all the attachment data in one SQL call; but it seems cluncky to me to have to then parse the string. Its also not very "relational"
However using an attachment Id, if I want to get the attachments, I either JOIN both tables on attachmentId and then have number of attachments x number of tasks returned. I can have up to 5 attachments so for 50 tasks, it could return 250 rows of which the first columns (from the task table side of the JOIN) are repeated and this seems like a waste. Obviously I have a little more than just description in my table!!!
I have also considered just getting the task data and then just getting the attachment data separately and then joining them in my application. This returns less data than the second option, but requires two calls to the database and that seems wrong too.
I am doing this wrong? Is there a better way? Does anyone have any thoughts on the best way to do this.
I'm not very confident with SQL and maybe I have missed something huge so any pointers would be gratefully received.

The right design is obviously two tables. Having only one table violates the first normal form.
Relating to the load problem, both approaches are correct.
Joining the tables in the sql statement is what most ORM's do to eagerly load related objects. Obviously there is some network traffic overhead, but I think it is acceptable.
Executing two separate sql statements is also correct. You can send them together in one batch to SQL Server to save roundtrips. It has a disadvantage although, you need to perform the join at the client side.
So, are you willing to write more code to save some network traffic?
EDIT:
Given the following table and data:
CREATE TABLE Tasks
(
TaskId int IDENTITY(1,1) PRIMARY KEY,
TaskDescription nvarchar(500) NOT NULL
)
CREATE TABLE TaskAttachments
(
AttachmentId int IDENTITY(1,1) PRIMARY KEY,
TaskId int NOT NULL REFERENCES Tasks(TaskId),
[FileName] nvarchar(500) NOT NULL,
[FileLocation] nvarchar(500) NOT NULL
)
GO
INSERT INTO Tasks VALUES
('Task1'), ('Task2')
INSERT INTO TaskAttachments VALUES
(1, 'FileName1', 'File location 1'),
(1, 'Filename2', 'File location 2'),
(2, 'FileName3', 'File location 3'),
(2, 'Filename4', 'File location 4')
The following classes:
public class TaskAttachment
{
public int AttachmentId { get; set; }
public string FileName { get; set; }
public string FileLocation { get; set; }
}
public class AppTask
{
public int TaskId { get; set; }
public string TaskDescription { get; set; }
public List<TaskAttachment> Attachments { get; set; }
public AppTask()
{
this.Attachments = new List<TaskAttachment>();
}
}
The following class loads the tasks with its attachments by executing two select statements in one single batch:
public class DataLayer
{
private readonly SqlConnection connection;
public DataLayer(SqlConnection connection)
{
this.connection = connection;
}
public List<AppTask> GetTasks()
{
var commandText = #"
SELECT TaskId, TaskDescription FROM Tasks;
SELECT AttachmentId, TaskId, [FileName], FileLocation FROM TaskAttachments;
";
using (var cmd = new SqlCommand(commandText, connection))
using (var reader = cmd.ExecuteReader())
{
var tasks = new List<AppTask>();
while (reader.Read())
{
var task = new AppTask
{
TaskId = reader.GetInt32(0),
TaskDescription = reader.GetString(1)
};
tasks.Add(task);
}
var taskDic = tasks.ToDictionary(x => x.TaskId);
reader.NextResult();
while (reader.Read())
{
var attachment = new TaskAttachment
{
AttachmentId = reader.GetInt32(0),
TaskId = reader.GetInt32(1),
FileName = reader.GetString(2),
FileLocation = reader.GetString(3)
};
var task = taskDic[attachment.TaskId];
task.Attachments.Add(attachment);
}
return tasks;
}
}
}
You can use the above class like this:
using (var cn = new SqlConnection("Data Source=(local);Initial Catalog=Tests;Integrated Security=SSPI"))
{
var dataLayer = new DataLayer(cn);
cn.Open();
var tasks = dataLayer.GetTasks();
}

Related

sqlite in Xamarin.Froms how to Update individual record in Local DB

I am trying to update individual records in sqlite database. I know how to Insert and delete records. I'd like to Update an individual record in a similar way to how I am deleting an individual record below. This uses a linq statement to get the record by Asset ID. I'd then like to pass my data to this to update.
I've also included how I insert a new record for reference. Does anybody have an example that they could share?
Delete an existing record
using (SQLiteConnection localconn = new SQLiteConnection(App.FilePath))
{
localconn.CreateTable<Road_Inspections>();
localconn.Table<Road_Inspections>().Where(x => x.Unique_ID == unique_ID).Delete();
}
Insert new record
Road_Inspections lri = new Road_Inspections()
{
ID = id,
Road_ID = Road_ID.Text.ToString(),
Asset_ID = Asset_ID.Text.ToString(),
Defect_Type = txtDefectType.Text.ToString(),
Response = txtResponse.Text.ToString(),
Inspection_Date = DateTime.Now,
};
using (SQLiteConnection conn = new SQLiteConnection(App.FilePath))
{
conn.CreateTable<Road_Inspections>();
int rowsAdded = conn.Insert(lri);
await DisplayAlert("Success", "Inspeciton Saved to Device", "OK");
}
You need a primary Key or Id in your object Announcement to identify your unique object in your database, for example:
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
Since you want to update you have to get the original entry from the database first.
And then you can edit and update it. So, you don't need to delete it before you insert a new one.
In xamarin form you can use nuget sqlite-net-pcl to achieve this.
Please refer to the following code:
public Task<int> SaveItemAsync(TodoItem item)
{
if (item.ID != 0)
{
return Database.UpdateAsync(item);
}
else
{
return Database.InsertAsync(item);
}
}
For more details,you can check: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/data-cloud/data/databases .
And there is a sample included in above document, you can check it here:https://learn.microsoft.com/en-us/samples/xamarin/xamarin-forms-samples/todo/

How to make a primary key start from 1000? sqlite.net xamarin android [duplicate]

This question already has answers here:
Set start value for AUTOINCREMENT in SQLite
(7 answers)
Closed 5 years ago.
I need the primary key to start from 1000 .how can do it?
public class LoginTable
{
[PrimaryKey, AutoIncrement, Column("_Id")]
public int id { get; set; }
public string info { get; set; }
public string info2 { get; set; }
}
You can modify the sqlite_sequence table and assign whatever starting point you want. If the LoginTable has not been inserted into, you will need to insert a new sqlite_sequence record, otherwise you would need to update the existing sqlite_sequence record.
The sqlite_sequence contains just two fields (name and seq) and does not contain a primary key nor any indexes. name is name of your table and seq is the current used sequence number, the next insert would increment this BEFORE using it.
Example:
using (var db = new SQLiteConnection("foo.sqlite"))
{
db.CreateTable<LoginTable>();
db.Execute("insert into sqlite_sequence (name, seq) values (?, ?);", new object[] { "LoginTable", 999 });
// if LoginTable records already exist, then the `sqlite_sequence` record exists for this table and you need to update it...
//db.Execute("update sqlite_sequence set seq = ? where name = ?;", new object[] { 999, "LoginTable"});
var login = new LoginTable { info = "info1....", info2 = "info2....." };
db.Insert(login);
db.Insert(login);
db.Insert(login);
foreach (var item in db.Table<LoginTable>())
{
Console.WriteLine(item.id);
}
}
Output:
1000
1001
1002

OrientDB execute script asynchronously and fetch records in a lazy fashion

Currently, we are using the Document API in OrientDB version 2.2. Let us suppose we have a class Company and a class Employee. Let's suppose we are interested in all Companies with at least one employee having a name from an arbitrary list. Employees are defined as LINKEDLISTs in our Company schema.
Our query would look smth like this:
select from Company where employees in (select from Employee where name in ["John", "Paul"])
Currently we have defined the following two indexes:
Company.employees (index on the employee links (their #rid)) -> dictionary hash index and
Employee.name -> notunique index
When executing the above query with explain we see that only the second index Employee.name is used, since we did not define the above indexes as a compound index. AS far as I could understand compound indexes across different classes like in our case are not supported in Orient 2.x.
Queries like this:
select from Company let $e = select from employees where name in ["John", "Paul"] where employees in $e
do not solve our problem either.
Searching across different blogs revealed two suggestions so far:
trying to define a compound index via inheritance by introducing a parent class on employee and company and defining the above two indexes on that
https://github.com/orientechnologies/orientdb/issues/5069
bundle the two queries in a batch scrip like this:
https://github.com/orientechnologies/orientdb/issues/6684
String cmd = "begin\n";
cmd += "let a = select from Employees where name " + query + "\n";
cmd += "let b = select from Company where employees in $a\n";
cmd += "COMMIT\n";
cmd += "return $b";
Suggestion 1 did not work for us.
Suggestion 2. worked. Both indexes have been used in each separate query, but then we ran into the next limitation of Orient. Batch scripts seem to be executed only synchronously, meaning that we can only get the results as a list all at once and not one by one in a lazy fashion, which in our case is a NO GO due to the memory overhead.
One naive workaround we tried is as follows:
public class OCommandAsyncScript extends OCommandScript implements OCommandRequestAsynch{
public OCommandAsyncScript(String sql, String cmd) {
super(sql, cmd);
}
#Override
public boolean isAsynchronous() {
return true;
}
private void containsAtLeastOne(final #Nonnull ODatabaseDocumentTx documentTx,
final #Nonnull Consumer<Company> matchConsumer,
final #Nonnull String queryText
) throws TimeoutException {
String cmd = "begin\n";
cmd += "let a = select from Employee where name " + queryText + "\n";
cmd += "let b = select from Company where employees in $a\n";
cmd += "COMMIT\n";
cmd += "return $b";
final OCommandHandler resultListener = new OCommandHandler(documentTx, (document -> {
final Company companies = document2model(document);
matchConsumer.accept(company);
}));
OCommandAsyncScript request = new OCommandAsyncScript("sql", cmd);
request.setResultListener(resultListener);
documentTx.command(request).execute();
...
}
}
public class OCommandHandler implements OCommandResultListener {
private final ODatabaseDocumentTx database;
private final Consumer<ODocument> matchConsumer;
public OCommandHandler(
final #Nonnull ODatabaseDocumentTx database,
final #Nonnull Consumer<ODocument> matchConsumer
) {
this.database = database;
this.matchConsumer = matchConsumer;
}
#Override
public boolean result(Object iRecord) {
if (iRecord != null) {
final ODocument document = (ODocument) iRecord;
/*
Result handler might be asynchronous, if document is loaded in a lazy mode,
database will be queries to fetch various fields. Need to activate it on the current thread.
*/
database.activateOnCurrentThread();
matchConsumer.accept(document);
}
return true;
}
...
}
The approach of defining a custom OCommandAsyncScript did not work unfortunately. When debugging the OStorageRemote class of Orient it seems that no partial results could be read, Here the respective extract from the source code:
public Object command(final OCommandRequestText iCommand) {
....
try {
OStorageRemote.this.beginResponse(network, session);
List<ORecord> temporaryResults = new ArrayList();
boolean addNextRecord = true;
byte status;
if(asynch) {
while((status = network.readByte()) > 0) {
ORecord record = (ORecord)OChannelBinaryProtocol.readIdentifiable(network);
if(record != null) {
switch(status) {
case 1:
if(addNextRecord) {
addNextRecord = iCommand.getResultListener().result(record);
database.getLocalCache().updateRecord(record);
}
break;
case 2:
if(record.getIdentity().getClusterId() == -2) {
temporaryResults.add(record);
}
database.getLocalCache().updateRecord(record);
}
}
}
}
}
Network.readbyte() is always null, hence no records could be fetched at all.
Is there any other workaround how we could execute a sql script in asynchronus mode and retrieve results in a lazy fashion preventing the generation of large lists on our application side?

List take time to insert data from database

I have list of class which class has 3 properties like this.
public string attributeName { get; set; }
public string strFormId { get; set; }
public string strValue { get; set; }
I am adding my database data into it through list like this
List<myAttributeData> attributesData = new List<myAttributeData>();
var result = db.ExecuteQuery<myAttributeData>(query, new object[0]);
// attributesData.Clear();
foreach (myAttributeData item in result.ToList())
{
if (item.attributeName == "Province ")
{
var Loc = from d in db.tblLocations
where d.LocationId == Convert.ToInt32(item.strValue)
select new
{
d.LocationName
};
foreach (var item1 in Loc.ToList())
{
attributesData.Add(new myAttributeData()
{
attributeName = item.attributeName,
strFormId = item.strFormId,
strValue = item1.LocationName
});
}
}
The problem is its taking so much time right now i have 70 thousands record into my database table which is take more than half hour any suggestion about this situation thanks. I have to add my data into list because I need it to use it back when it is needed can anybody give me solutions to cut the time of adding data into string.
One word: caching.
The problem with this code is that you're iterating through your 70,000 records, and for each one, you're going back to the database, to read in extra information.
foreach (myAttributeData item in result.ToList())
{
if (item.attributeName == "Province ")
{
var Loc = from d in db.tblLocations
where d.LocationId == Convert.ToInt32(item.strValue)
You'll find your code flies if you can cache the list of locations (before calling your foreach loop)
List<Location> cachedLocations = db.tblLocations.ToList();
..and then set your Loc variable from there:
var Loc = from d in cachedLocations
where d.LocationId == Convert.ToInt32(item.strValue)
Always keep the number of calls back to the database as low as possible.
Good luck!

Insert record using entity Framework (database first)

there are 3 database tables (movies, reviews, users)
the reviews table include ( MemeberID, MovieID, Review Text, Rate, ReviewDate)
(the MemeberID, and MovieID in the Review are the FK of the members table and the movies table)
The Movie can have many reviews, and i'm trying to add review to a movie
even I have movie class and the member class, I have a problem, in order to insert review, i need to reference it to movie and users , link them, and i don't know how to do it
this code make a error:
" The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects. "
This is my code...
public bool InsertNewReview(Movie _TheMovie, Member _TheMember, string _Text, byte _Rate, DateTime _ReviewDate)
{
Review ReviewToInsert = new Review()
{
MovieID = _TheMovie.MovieID,
MemberID = _TheMember.MemberID,
Movie = _TheMovie,
Member = _TheMember,
Rate = _Rate,
ReviewDate = _ReviewDate,
ReviewText = _Text
};
videoLib.Reviews.AddObject(ReviewToInsert);
videoLib.SaveChanges();
return true;
}
..
there are more data to insert to the Review class
Images: here
..
and the tables: (the "all columns" isn't a field in database tables)
Images: here
could you try like this
Review ReviewToInsert = videoLib.Reviews.CreateObject();
ReviewToInsert.MovieID = _TheMovie.MovieID
...
...
videoLib.Reviews.AddObject(ReviewToInsert);
videoLib.SaveChanges();
I got a solution, I need to define only the MovieID, MemberID, and not using their object
and use try & catch, to detect if thier the same MovieID (fk) and MemberID (fk) in the same row (because the review don't have is own id in the database)
public bool InsertNewReview(string _MovieID, int _MemberID, string _Text, byte _Rate, DateTime _ReviewDate)
{
try
{
Review ReviewToInsert = new Review()
{
Rate = _Rate,
ReviewDate = _ReviewDate,
ReviewText = _Text,
MovieID = _MovieID,
MemberID = _MemberID
};
videoLib.Reviews.AddObject(ReviewToInsert);
videoLib.SaveChanges();
return true;
}
catch
{
return false;
}
}

Resources