Save files in database with entity framework - c#

I have an ASP.NET MVC solution built on Entity Framework with Microsoft SQL Server 2008. I need to create a function that lets my users upload files.
What I would like is:
A solution that uses the Entity Framework to store files in the Database
A solution that detects and prevents from uploading the same file twice via some kind of hash/checksum
Tips on database/table design

In your entity model, map the BLOB database column to a byte[] property. Assign the content of the uploaded file to that property of the entity object, and save changes in the ObjectContext.
To compute a hash, you can use the MD5CryptoServiceProvider class

The "right" way to store a file in a SQL Server 2008 database is to use the FILESTREAM data type. I'm not aware that the Entity Framework supports that, but you can certainly try and see what happens.
That said, most of the time when people do this, they don't store the file in the database. Doing so means that you need to go through ASP.NET and the database server just to serve a file which you could be serving directly from the web server. It can also somewhat complicate the backup picture for your database and site. So when we upload files to our MVC/Entity Framework, we store only a reference to the file location in the database, and store the file itself elsewhere.
Obviously, which strategy is right for you depends a lot on the particulars of your application.

Here's how I do it for Podcasts:
ID Title Path Summary UploadDate
--- ----- -------- ---------------- -----------
1 TestPodcast /Podcasts/ep1.mp3 A test podcast 2010-02-12
The path stores a reference to the physical location of the Podcast. I used a post from Scott Hanselman on File Uploads with ASP.NET MVC to deal with the file upload part.

A working example (only for file upload because this one comes first in google) on basis of #Thomas's answer :
public void AddDocument(HttpPostedFileBase file)
{
try
{
using (TransactionScope scope = new TransactionScope())
{
try
{
using (var ctx = new Entities())
{
EntityDoc doc = new EntityDoc(); //The document table
doc.DocumentFileName = file.FileName; //The file Name
using (var reader = new System.IO.BinaryReader(file.InputStream))
{
doc.DocumentFile = reader.ReadBytes(file.ContentLength); // the Byte [] Field
}
ctx.EntityDocs.Add(doc);
ctx.SaveChanges();
scope.Complete();
}
}
catch (Exception ex)
{
throw ex;
}
}
}
catch (Exception ex)
{
throw ex;
}
}

Related

Attached images in Folder or Database?

I'm currently working on a .NET (core 3.1) website project and I am a little stuck on how to handle images and as I could not find a proper response for my case, here it is.
I'm working on a reports system where the user should be allowed to create a report and attach images if necessary. My question is, should I store the images in a database or a folder? The images will not contain "National security threats" but I guess they could be of a private nature.
Is it a good practice to store them on a Database?
I found it a bit messy the procedure to store them:
public async Task<IActionResult> Create(IFormFile image)
{
if (ModelState.IsValid)
{
byte[] p1 = null; //As I understand, it should be store as byte[]
using (var fs1 = image.OpenReadStream())
using (var ms1 = new MemoryStream())
{
fs1.CopyTo(ms1);
p1 = ms1.ToArray();
}
Image img = new Image(); //This is my Image model
img.Img = p1; //The property .IMG is of type "varbinary" on the DB.
_imagesDB.Images.Add(img); //My context
await _imagesDB.SaveChangesAsync();
return RedirectToAction(nameof(Index)); //if everything went well go back to index-
}
return View(report);
}
This is more or less ok (I guess) but I was not able to read it back from the database and send it to the View for showing.
Any ideas on how to read back the images from my context and, specially, how to send it from the controller to the View?
Thanks in advance.-
Alvaro.
There are pros and cons of both methods of storing files. It's convenient to have your files where your data is - however it takes a toll on the database side.
Text (the file path) in the database is only a few thousand bytes max (varchar data type, not the text data type in SQL), while a file can be enormous.
Imagine you wanted to query 1,000,000 users (hypothetically) - you would also be querying 1,000,000 files. That an enormous amount of data. Storing text (the file path) is minimal and a query could retrieve 1,000,000 rows of text rather quickly.
This can slow down your web app by causing longer load times due to your queries. I've had this issue personally and had to actually make a lazy load workaround to speed up the app.
Also, you have to consider the backup/restore process for your database. The larger the database then the longer your backup/restore times will take - and databases only grow. I heard a story about a company who backed up their database nightly, but their backup time took longer than a day due to files in their database. They weren't even done with the backup the day prior when the next backup started.
There are other factors to consider but those few alone are significant considerations.
In regards to the C# view/controller process...
Files are stored as bytes in a database (varbinary). You'll have to query the data and store them in a byte[] just like you are now and convert it to a file.
Here's a simplified snippet of one of my controllers in my .NET Core 3.1 web app.
This was only to download 1 PDF file - you will have to change it for your needs of course.
public async Task<IActionResult> Download(string docId, string docSource)
{
// Some kinda of validation...
if (!string.IsNullOrEmpty(docId))
{
// These are my query parameters (I'm using Dapper)
var p = new
{
docId,
docSource // This is just a parameter for my specific query
};
// Query the database for the document
// DocumentModel doc = some kinda of async query using
// the p variables as parameters
// I cut this part out since your database methods may be different
try
{
// Return the file
return File(doc.Content, "application/pdf", doc.LeafName);
}
catch
{
// You'll probably want to pass some kind of error message to your view
return View();
}
}
return View();
}
The doc.Content are the bytes and the doc.LeafName is just the name of the document.
You can also pass the file back to your View by setting properties on it's ViewModel/Model.
return View(new YourViewModel
{
SomeViewModelProperty = someProp,
Documents = documents
});
If you use a file server that's accessible to your API or web app then I believe you can retrieve the file directly from there.

Caching posted data and fall-backs

I'm currently working on a project that has an external site posting xml data to a specified url on our site. My initial thoughts were to first of all save the xml data to a physical file on our server as a backup. I then insert the data into the cache and from then on, all requests for the data will be made to the cache instead of the physical file.
At the moment I have the following:
[HttpPost]
public void MyHandler()
{
// filePath = path to my xml file
// Delete the previous file
if (File.Exists(filePath))
File.Delete(filePath));
using (Stream output = File.OpenWrite(filePath))
using (Stream input = request.InputStream)
{
input.CopyTo(output);
}
// Deserialize and save the data to the cache
var xml = new XmlTextReader(filePath);
var serializer = new XmlSerializer(typeof(MyClass));
var myClass = (MyClass)serializer.Deserialize(xml);
HttpContext.Current.Cache.Insert(myKey,
myClass,
null,
myTimespan,
Cache.NoSlidingExpiration,
CacheItemPriority.Default, null);
}
The issue I have is that I'm always getting exceptions thrown because the file that I'm saving to 'is in use' when I try a second post to update the data.
A colleague suggested using a Mutex class just before I left work on the Friday so I wonder if that is the correct approach here?
Basically I'm just trying to sanity check that this is a good way of managing the data? I can see there's clearly an issue with how I'm writing the data to a file but aside from this, does my approach make sense?
Thanks

C# - Save Variables of a Program

I'm making a game now in C# (which is a Console Application) and its variables need to be saved.
I've tried using Settings but there's a big problem about it: If the file name is changed or the file is transferred to somewhere else, the Settings are lost.
So what is a good alternative to Settings for saving variables and retrieving them later in the application?
EDIT: I'd like to save the variables to a text file and retrieve it later, is it possible? If yes, then how?
And please don't suggest online servers, because I'm working on a singleplayer game without keeping tracks of the players whatsoever.
One simple way to store data of a fixed type is serialization with the BinaryFormatter class.
See the MSDN documentation for Binary Formatter. I've copied some of the relevant code here.
using System;
using System.IO;
using System.Collections;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;
void SaveData()
{
// Create a hashtable of values that will eventually be serialized.
Hashtable addresses = new Hashtable();
addresses.Add("Jeff", "123 Main Street, Redmond, WA 98052");
addresses.Add("Fred", "987 Pine Road, Phila., PA 19116");
addresses.Add("Mary", "PO Box 112233, Palo Alto, CA 94301");
// To serialize the hashtable and its key/value pairs,
// you must first open a stream for writing.
// In this case, use a file stream.
FileStream fs = new FileStream("DataFile.dat", FileMode.Create);
// Construct a BinaryFormatter and use it to serialize the data to the stream.
BinaryFormatter formatter = new BinaryFormatter();
try
{
formatter.Serialize(fs, addresses);
}
catch (SerializationException e)
{
Console.WriteLine("Failed to serialize. Reason: " + e.Message);
throw;
}
finally
{
fs.Close();
}
}
void LoadData()
{
// Declare the hashtable reference.
Hashtable addresses = null;
// Open the file containing the data that you want to deserialize.
FileStream fs = new FileStream("DataFile.dat", FileMode.Open);
try
{
BinaryFormatter formatter = new BinaryFormatter();
// Deserialize the hashtable from the file and
// assign the reference to the local variable.
addresses = (Hashtable) formatter.Deserialize(fs);
}
catch (SerializationException e)
{
Console.WriteLine("Failed to deserialize. Reason: " + e.Message);
throw;
}
finally
{
fs.Close();
}
// To prove that the table deserialized correctly,
// display the key/value pairs.
foreach (DictionaryEntry de in addresses)
{
Console.WriteLine("{0} lives at {1}.", de.Key, de.Value);
}
}
If your app structure is dynamic by it's nature, so it's name can be changed, location can be changed (even if , to be honest, don't understand reasons behind that) the only possibility I can see is relay in external source for retrieving or storing your config information.
In short: setup a server somewhere that holds your app configuration data, and on first startup try to reach that server, load file from it, read a data. If it fails, just load default information.
Good candidates could be : DropBox, SkyDrive, GoogleDrive, Box... find suitable C# API for any of them an store/read data you need. The only thing I would invite your attention to for this solution, is licensing. Keep an eye on it, and be sure that you can use it in your application in a way you decide to use it.
Saving the values out to a flat file...
Storing the values in an XML File, or a database file...
Windows Registry...
There are many places you can store information, and only experience will really teach you what to put where... To make an intelligent guess, you need to be familiar with all the approaches...
The only real option that isn't susceptible to the user intentionally changing the data stored on their computer, losing it due to changing machines, etc. would be to not store the data on their computer at all. Have a database or other server that you host that users connect to over the network which stores their data for them.
You may try to use the Isolated Storage
Isolated storage is not available for Windows Store apps. Instead, use
the application data classes in the Windows.Storage namespaces
included in the Windows Runtime API to store local data and files.
You may also try to use XML file to store the users setting and then store it in the SpecialFolder.ApplicationData directory.
You can also use the app.config file to save application-level settings

SQL Server: How to copy a file (pdf, doc, txt...) stored in a varbinary(max) field to a file in a CLR stored procedure?

I ask this question as a followup of this question.
A solution that uses bcp and xp_cmdshell, that is not my desired solution, has been posted here.
I am new to c# (since I am a Delphi developer) anyway I was able to create a simple CLR stored procedure by following a tutorial.
My task is to move a file from the client file system to the server file system (the server can be accessed using remote IP, so I cannot use a shared folder as destination, this is why I need a CLR stored procedure).
So I plan to:
store from Delphi the file in a varbinary(max) column of a temporary table
call the CLR stored procedure to create a file at the desired path using the data contained in the varbinary(max) field
Imagine I need to move C:\MyFile.pdf to Z:\MyFile.pdf, where C: is a harddrive on local system and Z: is an harddrive on the server. C is in New York, Z is in London and there is no VPN between them, just https connection.
I provide the code below (not working) that someone can modify to make it work? Here I suppose to have a table called MyTable with two fields: ID (int) and DATA (varbinary(max)). Please note it doesn't make a difference if the table is a real temporary table or just a table where I temporarly store the data. I would appreciate if some exception handling code is there (so that I can manage an "impossible to save file" exception).
I would like to be able to write a new file or overwrite the file if already existing.
[Microsoft.SqlServer.Server.SqlProcedure]
public static void VarbinaryToFile(int TableId)
{
using (SqlConnection connection = new SqlConnection("context connection=true"))
{
connection.Open();
SqlCommand command = new SqlCommand("select data from mytable where ID = #TableId", connection);
command.Parameters.AddWithValue("#TableId", TableId);
// This was the sample code I found to run a query
//SqlContext.Pipe.ExecuteAndSend(command);
// instead I need something like this (THIS IS META_SYNTAX!!!):
SqlContext.Pipe.ResultAsStream.SaveToFile('z:\MyFile.pdf');
}
}
(one subquestion is: is this approach correct or there is a way to directly pass the data to the CLR stored procedure so I don't need to use a temp table?)
If the subquestion's answer is No, could you describe the approach of avoiding a temp table? So is there a better way then the one I describe above (=temp table + Stored procedure)? A way to directly pass the dataastream from the client application to the CLR stored procedure? (my files can be any size but also very big)
is [there] a way to directly pass the data to the CLR stored procedure so I don't need to use a temp table?
Yes, it is both possible and rather simple to pass a binary file to a SQLCLR stored procedure and have it write the contents to disk, and not require first placing those contents into a table--temporary or real.
[Microsoft.SqlServer.Server.SqlProcedure]
public static void SaveFileToLocalDisk([SqlFacet(MaxSize = -1)] SqlBytes FileContents,
SqlString DestinationPath)
{
if (FileContents.IsNull || DestinationPath.IsNull)
{
throw new ArgumentException("Seriously?");
}
File.WriteAllBytes(DestinationPath.Value, FileContents.Buffer);
return;
}
Or, since you said that the files are sometimes large, the following should be much easier on memory usage as it makes use of the streaming functionality:
[Microsoft.SqlServer.Server.SqlProcedure]
public static void SaveFileToLocalDiskStreamed(
[SqlFacet(MaxSize = -1)] SqlBytes FileContents, SqlString DestinationPath)
{
if (FileContents.IsNull || DestinationPath.IsNull)
{
throw new ArgumentException("Seriously?");
}
int _ChunkSize = 1024;
byte[] _Buffer = new byte[_ChunkSize];
using (FileStream _File = new FileStream(DestinationPath.Value, FileMode.Create))
{
long _Position = 0;
long _BytesRead = 0;
while (true)
{
_BytesRead = FileContents.Read(_Position, _Buffer, 0, _ChunkSize);
_File.Write(_Buffer, 0, (int)_BytesRead);
_Position += _ChunkSize;
if (_BytesRead < _ChunkSize || (_Position >= FileContents.Length))
{
break;
}
}
_File.Close();
}
return;
}
The assembly containing this code will, of course, need to have a PERMISSION_SET of EXTERNAL_ACCESS.
In both cases, you would execute them in the following manner:
EXEC dbo.SaveFileToLocalDiskStreamed 0x2A20202A, N'C:\TEMP\SaveToDiskTest.txt';
And "0x2A20202A" should give you a file containing the following 4 characters (asterisk, space, space, asterisk):
* *
See http://www.mssqltips.com/tip.asp?tip=1662
Why are you putting these files into database? If you have http/https connection you can upload the file to the server, write into a protected dierctory and create a page to list those files and give a link to download it. If you want to store some extra information you can write it into database. You just need to change the name of file at server side (use a unique name).
After some research I conclude that it makes no sense, it is better to drop the support of 2005 and use 2008 fielstream feature. I can have a conditional logic to choose between 2005 and 2008 and use filestream only for 2005.

New Access database, how can it be done?

I have a project in C# using Microsoft Office Access for storage. I can read and save to the database.
Now I need to allow the user to use the new database project but structured like the working one, and also to implement Save As option.
Besides I need to export to a text file/CSV.
Any ideas or sample codes would be helpful.
One way to create a blank DB is to try the following
using System;
using ADOX;
public class CreateDB
{
public static void Main( string [] args )
{
ADOX.CatalogClass cat = new ADOX.CatalogClass();
string create =
#"Provider=Microsoft.Jet.OLEDB.4.0;Data
Source=C:\BlankAccessDB\MyAccessDBCreatedFromCsharp.mdb;" +
"Jet OLEDB:Engine Type=5";
cat.Create(create);
cat = null;
}
}
Both Save and SaveAs is as easy as using SaveFileDialog to prompt the user to specify the filename and location to save the file.
The way I did this was to create a new empty access database file (this comes to about 100 KB) and then embed that file as a resource in my application. To "create" a new database is then simply a matter of extracting the resource to a file - which gives you a blank database - and then running a schema update code to create the schema you require in the blank database and then off you go.
I have a project that contains an empty database set to be embedded, a class with one method as below and, er, that's about it.
This is the code to dump the file from the embedded resource - it's not up to date, I wrote it 6 years ago but have had no need to change it:
public void CreateDatabase(string sPath)
{
// Get the resource and, er write it out?
System.IO.Stream DBStream;
System.IO.StreamReader dbReader;
System.IO.FileStream OutputStream;
OutputStream = new FileStream(sPath, FileMode.Create);
Assembly ass = System.Reflection.Assembly.GetAssembly(this.GetType());
DBStream = ass.GetManifestResourceStream("SoftwareByMurph.blank.mdb");
dbReader = new StreamReader(DBStream);
for(int l=0;l < DBStream.Length;l++)
{
OutputStream.WriteByte((byte)DBStream.ReadByte());
}
OutputStream.Close();
}
Simple, effective and the .dll is 124 KB.
Note I use an utterly blank and empty Access file - attempting to maintain the right schema in the embedded file is going to cause it to grow (because of the way .mdb files work) and may result in shipping data - which probably shouldn't happen. The schema itself is created/updated/maintained by a separate lump of DDL (SQL) that I run from code.
Export to .CSV is moderately trivial to do by hand since you pretty much just need to iterate over the columns in a table but for a smarter approach look at FileHelpers.

Categories