Initialize database table from existing .tsv file - c#

I have an ASP.NET MVC 5 project and I want to fill a certain table in the database with information from a .tsv file.
These are what the first 3 lines of the file look like:
CAE_Num CAE_Description
01111 Description 1
01112 Description 2
So I made a model/class that looks like this:
namespace project.Models
{
public class CAE
{
public int Id { get; set; } // id
public int CAE_Num { get; set; }
public string CAE_Description { get; set; }
public static CAE FromTsv(string tsvLine)
{
string[] values = tsvLine.Split('\t');
CAE cae = new CAE();
cae.CAE_Num = Convert.ToInt32(values[0]);
cae.CAE_Description = Convert.ToString(values[1]);
return cae;
}
}
}
The model includes a function that splits a string and creates a CAE object based on it.
In order to fill the database before runtime, I decided to use the Seed method in the Configuration class, created when you enable database migrations. I've used this in a different project before, for user roles, so I know this is one of the right places I can achieve what I want.
So here's what I did:
namespace project.Migrations
{
using System;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
using project.Models;
using System.IO;
using System.Collections.Generic;
using System.Web;
internal sealed class Configuration : DbMigrationsConfiguration<ApplicationDbContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(ApplicationDbContext context)
{
List<CAE> listCAEs = File.ReadAllLines(HttpContext.Current.Server.MapPath("~/App_Data/CAE.tsv")) // reads all lines into a string array
.Skip(1) // skip header line
.Select(f => CAE.FromTsv(f)) // uses Linq to select each line and create a new Cae instance using the FromTsv method.
.ToList(); // converts to type List
listCAEs.ForEach(s => context.CAEs.Add(s));
context.SaveChanges();
}
}
}
When I run update-database I get the error/warning:
Object reference not set to an instance of an object.
and my model isn't filled at all when I go to localhost:xxxx/CAEs, nor is any information added to the dbo.CAEs [Data] table in the Server Explorer.
I am wondering if my issue is with the path to the .tsv file. I googled and I read that having the file in the App_Data folder saves me the trouble of hardcoding a file path.

For anyone reading this in the future, I placed the function from SteveGreene's link in the Configuration class, above all other methods. In this function I only changed AbsolutePath to LocalPath.
Then on the Seed method I changed the line
List<CAE> listCAEs = File.ReadAllLines(HttpContext.Current.Server.MapPath("~/App_Data/CAE.tsv"))
to
List<CAE> listCAEs = File.ReadAllLines(MapPath("~/App_Data/CAE.tsv"))

Related

Storing (and using) file naming schemas to a class property

I have a project that contains a stateProfile class and an Inquiry class.
The Inquiry class is static and it's job is to create a text file. The text file's contents are calculated using information stored in the properties of a stateProfile object (one for each State in the USA, that winds up being read in from an XML file).
I had been able to share a global naming schema for all of the states but I now have requirements to meet for naming them based on the stateProfile object.
For example:
I have a stateProfile for Kansas and another for Missouri.
Inquiry inquiry = new Inquiry();
foreach(stateProfile p in _listOfProfiles){
inquiry.CreateFile(p);
}
in Inquiry.CreateFile(stateProfile p) I have the expression that defines the filename:
sOutFileName = $"{p.prop1}_{p.prop2}_Literal3_{_staticProp.ToString("D4")}.txt";
Any suggestions for being able to store the logic/expression in a property like stateProfile.outFileName such as
public string outFileName {
$"{p.prop1}_{p.prop2}_Literal3_{_staticProp.ToString("D4")}.txt";
}
and then be able to refer to that property in Inquiry.CreateFile(stateProfile p) like the following?
sOutFileName = $"{p.outFileName}";
I think that this sort of approach might be what you are looking for. It allows you to embed C# scripts into your XML and interpret them in code.
Every State could have a different filename format now.
using System;
using Microsoft.CodeAnalysis.Scripting;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static async Task Main(string[] args)
{
// This was read from your XML file.
var filenameFormatterScript = "p => $\"{p.Name.ToLower()}.txt\"";
var options = ScriptOptions.Default.AddReferences(typeof(StateProfile).Assembly);
var sp = new StateProfile
{
Name = "Alabama",
FilenameFormatter = await CSharpScript.EvaluateAsync<Func<StateProfile, string>>(filenameFormatterScript, options)
};
Console.WriteLine(sp.Filename());
}
}
public class StateProfile
{
public string Name { get; set; }
public Func<StateProfile, string> FilenameFormatter { get; set; }
public string Filename()
{
return FilenameFormatter(this);
}
}
}
After thinking about this some more, Alex's comment was the way to go.
Since all of the properties I am looking to use in constructing the filename formulas are belonging to the stateProfile object, I make a method GetFileName() class member and pass in anything else I might need.
I use a switch statement, which could get unwieldy as the number of states I have to consider grows, but for now it works.
public string GetFileName(int prop1) {
string sOutFileName = "";
switch (SourceState) {
case "MO":
sOutFileName = $"ReqPref_{this.stProfPropA}_{prop1}.txt";
break;
default:
sOutFileName = $"{this.stProfPropB}_{prop1}.txt";
break;
}
return sOutFileName;
}

Why Find method get out of range MongDb C# driver 2.10.4?

Hi i try to duplicate my collection in the database. I have white the following Main:
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
static void Main(string[] args)
{
MongoCRUD db = new MongoCRUD("testClass");
/* copy the colection*/
var targetTable = "geo2";
var newTable = "NormalizeCordinatesGeo";
db.CopyColection<GlobalUrbanPoint>(targetTable, newTable);
}
The following class is for my model
public class GlobalUrbanPoint
{
[BsonId]
public ObjectId Id{ get; set; }
public double LATITUDE { get; set; }
public double LONGITUDE { get; set; }
...
}
For the the operation in my program i use use MongoCRUD class:
public class MongoCRUD
{
private IMongoDatabase db;
public MongoCRUD(string database)
{
var client = new MongoClient();
db = client.GetDatabase(database);
}
...
public void CopyColection<T>(string targetTable, string newTable)
{
var source_colection = db.GetCollection<T>(targetTable);
var dest = db.GetCollection<T>(newTable);
dest.InsertMany(source_colection.Find(new BsonDocument()).ToList());
}
}
To test the function CopyColection i have crate a colocation with 6 documents and it work as expected.
When i try to copy a larger collection more specific with 66.579 documents i get flowing error
System.FormatException: An error occurred while deserializing the TYPE property of class MongoDBCurve.Program+GlobalUrbanPoint: Index was outside the bounds of the array.
I do not understand do i need to specify the bounds? I need to create a batches because the collection is to large ?
Can someone explain to my why this occurs and what i need to do to copy properly my collection.
Thank you for your time.
I found the mistake that i made after try and error. The types of filed of my class it was different from the data in database.

Reusing Validation Attributes in MVC5

I have been doing a lot of research on validation and how it works. I understand that we can use attributes or even make custom attributes which we can throw over our ViewModels to validate that data. While this is all working fine, I am finding myself to be reusing same combination of attributes on multiple ViewModels.
For example, lets take a "Name", in project X a name, whether it is a Movie Name, Book Name, Person First Name, Last Name, etc... it is a name after all and as such I tend to apply 90% of the validation attributes the same. Required, Minimum length 3, Maximum length 50, only letters, spaces, etc... you get the picture.
Now I end up with a variable that has 5+ attributes stacked on it. These are pre-built attributes that I would prefer not to code again as they are already coded for me. So my question is this:
How can I create a CustomValidateName attribute, which will validate for all of those things, provide different error messages based on what is wrong and at the same time, reuse some of the built in attributes in .NET framework so that I am not re-inventing the wheel. The bottom line here is that whenever I have a Name variable, I can now just put this one attribute instead of the normal 5+.
Use Can create custom Validation for your all validation
For Example :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
namespace Custom_DataAnnotation_Attribute.Models
{
public class CustomEmailValidator : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
string email = value.ToString();
if (Regex.IsMatch(email, #"[A-Za-z0-9._%+-]+#[A-Za-z0-9.-]+\.[A-Za-z]{2,4}", RegexOptions.IgnoreCase))
{
return ValidationResult.Success;
}
else
{
return new ValidationResult("Please Enter a Valid Email.");
}
}
else
{
return new ValidationResult("" + validationContext.DisplayName + " is required");
}
}
Above method generate validation for both required nd Email type
You can Add More validation In this method Using If Else or Switch and revert the custome message
At Model:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
namespace Custom_DataAnnotation_Attribute.Models
{
public class EmployeeModel
{
public string Name { get; set; }
[CustomEmailValidator]
public string Email { get; set; }
public string Password { get; set; }
public string Mobile { get; set; }
}
}

Entity Framework MVC and multiple databases

I've put together an MVC application using a repository pattern with Entity Framework and everything is going smoothly - but I've run into a stopping block and I'm not sure how to proceed.
I have a few dozen databases with the same schema, and I want to be able to choose one or many at runtime. For example, let's say I start with a database of users (not made yet). That user has connection string information associated with them (possibly more than one). Once the user has "logged in", I want the Enumerables I feed to my Views to contain matching data from all of the databases that user has access to.
Here's an example of what I have right now:
Entity:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations.Schema;
namespace Dashboard.Domain.Entities
{
public class Flight
{
public Guid Id { get; set; }
public string CarrierCode { get; set; }
public string FlightNo { get; set; }
public string MarketingCarrierCode { get; set; }
public string MarketingFlightNo { get; set; }
public string Type { get; set; }
public string TailNo { get; set; }
public string OriginIATA { get; set; }
...
}
}
DB Context:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Entity;
using Dashboard.Domain.Entities;
namespace Dashboard.Domain.Concrete
{
public class EFDbContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Passenger>().ToTable("PAX");
}
public DbSet<Flight> Flights { get; set; }
public DbSet<Passenger> PAX { get; set; }
public DbSet<Airport> Airports { get; set; }
}
}
Flight repository interface:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Dashboard.Domain.Entities;
namespace Dashboard.Domain.Abstract
{
public interface IFlightRepository
{
IQueryable<Flight> Flights { get; }
}
}
EF Flight Repository:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Dashboard.Domain.Abstract;
using Dashboard.Domain.Entities;
namespace Dashboard.Domain.Concrete
{
public class EFFlightRepository : IFlightRepository
{
private EFDbContext context = new EFDbContext();
public IQueryable<Flight> Flights
{
get { return context.Flights; }
}
}
}
Controller:
public class FlightController : Controller
{
private IFlightRepository fRepository;
private IPaxRepository pRepository;
private IAirportRepository aRepository;
public int PageSize = 10;
public FlightController(IFlightRepository flightRepository, IPaxRepository paxRepository, IAirportRepository airportRepository)
{
this.fRepository = flightRepository;
this.pRepository = paxRepository;
this.aRepository = airportRepository;
}
public ViewResult List(byte status = 1, int page = 1)
{ ...
I want those repositories to contain all of the data from all of the connection strings specified, but I have no idea where to start. EF is getting my connection string from the web.config, but I need to be able to set it dynamically somehow and I need to put more than one database's data into the repository.
Is this possible? I should mention that the site is READ ONLY, so I won't need to write changes back to the DBs.
UPDATE:
I've changed the code so I can pass a connection string to the constructor of my EF Repository, but when I try to merge the IQueryables from two different contexts, as below:
public class EFFlightRepository : IFlightRepository
{
private EFDbContext context1 = new EFDbContext(connectionstring1);
private EFDbContext context2 = new EFDbContext(connectionstring2);
private IQueryable<Flight> context;
public EFFlightRepository()
{
context = (IQueryable<Flight>)context1.Flights.Union(context2.Flights);
}
public IQueryable<Flight> Flights
{
get { return context;}
}
}
I get this exception:
The specified LINQ expression contains references to queries that are
associated with different contexts.
How can I combine them so I can run my LINQ queries just like it's ONE set of data?
It is difficult to come up with a detailed solution because it really depends on your software design choices, but I think a possible solution consists of the following things:
1) A method / class that creates a collection of DbContext objects using the DbContext constructor with connection string or connection string name (is the same constructor) as Willian Werlang mentioned:
new DbContext("DB1");
2) Your repositories should be able to accept the list of DbContext's rather than a single one. It could e.g. be injected with the constructor of it.
3) The retrieval methods should iterate over the repositories and load (eager load when detaching) the relevant objects.
4) The retrieved objects could be detached from their DbContext using the following code:
dbContext.Entry(entity).State = EntityState.Detached;
This isn't required but might be a consideration since you would return a mix of different data sources.
5) The retrieved/detached objects should be added to a returned List<> or you could yield return the results one by one with IEnumerable<> is return type.
Returning an IQueryable isn't possible in this case but an IEnumerable will do as result.
An example of a simple retrieval method for a flight repository could be something like:
public IEnumerable<Flight> GetFlights() {
// dbContexts is an IEnumerable<DbContext> that was injected in the constructor
foreach (var ctx in dbContexts) {
foreach (var flight in ctx.Flights) {
yield return flight;
}
}
}
You can set multiples databases on your web.config, but with different names, so your DbContext's can receive the name of the database you want as parameter, like:
new DbContext("DB1");
This way you can choose from which database you'll get the data but I don't think you can get data from multiples bases at the same time with only onde dbContext;
My solution was to change my Repository classes to take a connection string parameter, like this:
namespace Dashboard.Domain.Concrete
{
public class EFFlightRepository : IFlightRepository
{
private EFDbContext context;
public IQueryable<Flight> Flights
{
get { return context.Flights;}
}
public EFFlightRepository(string connectionString)
{
context = new EFDbContext(connectionString);
}
}
}
Then create a factory class (using Ninject.Extensions.Factory) to pass the parameter when the repository is being created (How to pass parameters down the dependency chain using Ninject):
namespace Dashboard.Domain.Factories
{
public interface IFlightRepoFactory
{
IFlightRepository CreateRepo(string connectionString);
}
}
I have another Factory class that produces a list of Repositories based on a list of strings (connection strings to feed to the individual repository classes).
namespace Dashboard.Domain.Factories
{
public interface IRepoCollectionFactory
{
IRepositoryCollection CreateCollection(List<string> connectionStrings);
}
}
Then, in my controller class, I iterate through the Collection generated by the Collection Factory, running whatever query needs to be run on each set of repositories, and combine the results.
This ultimately gives me a list that contains all of the data from each query on each repository.
public FlightController(IRepoCollectionFactory repoCollectionFactory)
{
this.repoCollectionFactory = repoCollectionFactory;
this.collection = repoCollectionFactory.CreateCollection(new List<string> {
// each connection string for each database here
});
}
Bindings in Ninject class:
private void AddBindings()
{
ninjectKernel.Bind<IFlightRepoFactory>().ToFactory();
ninjectKernel.Bind<IAirportRepoFactory>().ToFactory();
ninjectKernel.Bind<IPaxRepoFactory>().ToFactory();
ninjectKernel.Bind<IRepoFactory>().ToFactory();
ninjectKernel.Bind<IRepoCollectionFactory>().ToFactory();
ninjectKernel.Bind<IRepositories>().To<EFRepositories>();
ninjectKernel.Bind<IRepositoryCollection>().To<EFRepositoryCollection>();
ninjectKernel.Bind<IFlightRepository>().To<EFFlightRepository>();
ninjectKernel.Bind<IPaxRepository>().To<EFPaxRepository>();
ninjectKernel.Bind<IAirportRepository>().To<EFAirportRepository>();
}

Accessing Database records from within .CS file, not working so well

I need to do a few db things and I would rather have extension methods in a separate file rather than having them all within the cshtml file.
But, I get the following error:
Compiler Error Message: CS0120: An
object reference is required for the
non-static field, method, or property
'UserOperations.GetFirstName(string)'
Which points me to this line:
Line 165: <li>Hi, #UserOperations.GetFirstName(WebSecurity.CurrentUserId)</li>
The code I'm using is: (CSHTML)
#if(WebSecurity.IsAuthenticated)
{
<li>Hi, #UserOperations.GetFirstName(WebSecurity.CurrentUserId.ToString())</li>
}
else
{
<li>My Account
<ul>
<li>Sign In</li>
<li>Create Account</li>
</ul>
</li>
}
And...: (UserOperations.CS)
using System;
using WebMatrix.WebData;
using WebMatrix.Data;
using System.Collections.Generic;
using System.Web;
/// <summary>
/// User & Database-based operations in this file only.
/// </summary>
public class UserOperations
{
public string GetFirstName(string CurrentUserID)
{
WebSecurity.InitializeDatabaseConnection("eee", "www", "www", "www", true);
var database = Database.Open("OSF");
var SelectQueryString = "SELECT FirstName FROM UserProfile WHERE FirstName = " + CurrentUserID;
var result = database.Query(SelectQueryString);
return result.ToString();
}
}
I don't get what the error is trying to say. I do this kinda stuff in C# Desktop apps all the time, I don't understand how or what I'm doing wrong? Can someone please help?
Thank you!
You are not instantiating the class, so you would need to make it static.
public static class UserOperations
{
public static string GetFirstName(string CurrentUserID)
{
More typically, though, you would have a User class that you would create an instance of in your controller, and pass that to your model. This will help you avoid doing discreet queries for each piece of information you need.
You may want to investigate the Repository Pattern.
Your UserOperations class shall look something like this
public static class UserOperations
{
public static string GetFirstName(this string CurrentUserID)
{
...
}
}

Categories

Resources