Entity Framework MVC and multiple databases - c#

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>();
}

Related

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.

How to store application sessions into Static instance correctly?

We are storing the all the application sessions into Global class.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.SessionState;
namespace MyApp
{
[Serializable]
public class AppSession : IRequiresSessionState
{
//Name that will be used as key for Session object
private const string SESSION_SINGLETON = "MyappSession";
string _UserName;
int? _UserID ,_ClientID;
public int? UserID
{
get
{
return _UserID;
}
set
{
_UserID = value;
}
}
public string UserName
{
get
{
return _UserName;
}
set
{
_UserName = value;
}
}
public int? ClientID
{
get
{
return _ClientID;
}
set
{
_ClientID = value;
}
}
public MarketingSession Marketing { get; set; }
}
}
We have created another class property to maintain that Page sessions into different MarketingSession class as below
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.SessionState;
namespace MyApp
{
[Serializable]
public class MarketingSession : IRequiresSessionState
{
private int? _ChannelId;
public int? ChallelId { get
{
return _ChannelId; }
set { _ChannelId = value; }
}
}
}
I am not 100% sure that is it correct way to maintain Session in class or not.
and Is it ok to create Non-Static another Class object in this Session.
I am getting error as below when I added MarketingSession Class property.
Error 10 An object reference is required for the non-static field,
method, or property MyApp.AppSession.Marketing.get
Update me if I am doing right way to manage my application sessions or not?
If way is correct then How to fix the error ?
The Singleton Pattern seems like the best solution in your case.
You want to initialize your Marketing object only once, and share its value among all of your AppSession class instances.
This is exactly what this pattern is used for.
Make sure to read some of the following examples on how to use it and have it suit your goals the best way.
https://msdn.microsoft.com/en-us/library/ff650316.aspx
http://csharpindepth.com/Articles/General/Singleton.aspx
https://www.dotnetperls.com/singleton

Initialize database table from existing .tsv file

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"))

How do I add a new table to a Telerik Open Access MVC project?

I've inherited a MVC project that seems to use Telerik Open Access to handle data instead of using something I'm more familiar with like entity framework. I'm trying to understand the whole concept of how to work with this data method, but right now I'm just needing to find out how to add a table. I've limited my code examples to one table, but in reality there are dozens of them.
So I see that the class OpenAccessContext.cs has a database connection string, but it also has a IQueryable item made up of the class tblMaterial. The tblMaterial class is defined in tblMaterial.cs. I don't understand how this class is connected to the SQL database version of tblMaterial (so feel free to educate me on that).
I have a table called tblContacts in the SQL database. What do I need to do to connect it to my project? There's no "update from database" option when I right click any object in the solution (because they're all just classes). Will I need to create a new class manually called tblContacts.cs? If so, how do I connect it to the database version of tblContacts? Am I going to need to manually change multiple classes to add the table (OpenAccessContext, MetadataSources, Repository, etc.)?
I tried to keep this as one simple question (how do I add a table) so I don't get dinged, but any light you can shine on the Telerik Open Access would be helpful. (Please don't ding me for asking that!) I checked out the Telerik documentation here: http://docs.telerik.com/data-access/developers-guide/code-only-mapping/getting-started/fluent-mapping-getting-started-fluent-mapping-api , but it's related to setting up a new open access solution. I need to know how to modify one (without ruining the already working code). Thank you in advance for your help!
Here's the solution as seen in Visual Studio:
Open Access
Properties
References
OpenAccessContext.cs
OpenAccessMetadataSources.cs
Repository.cs
tblMaterial.cs
Here's the code:
OpenAccessContext.cs
namespace OpenAccess
{
public partial class OpenAccessContext : OpenAccessContext
{
static MetadataContainer metadataContainer = new OpenAccessMetadataSource().GetModel();
static BackendConfiguration backendConfiguration = new BackendConfiguration()
{
Backend = "mssql"
};
private static string DbConnection = ConfigurationManager.ConnectionStrings["ConnString"].ConnectionString;
private static int entity = ConfigurationManager.AppSettings["Entity"] == "" ? 0 : int.Parse(ConfigurationManager.AppSettings["Entity"]);
public OpenAccessContext() : base(DbConnection, backendConfiguration, metadataContainer)
{
}
public IQueryable<tblMaterial> tblMaterials
{
get
{
return this.GetAll<tblMaterial>(); //.Where(a => a.EntityId == entity);
}
}
}
}
OpenAccessMetadataSources.cs
namespace OpenAccess
{
public class OpenAccessMetadataSource : FluentMetadataSource
{
protected override IList<MappingConfiguration> PrepareMapping()
{
var configurations = new List<MappingConfiguration>();
// tblMaterial
var materialConfiguration = new MappingConfiguration<tblMaterial>();
materialConfiguration.MapType(x => new
{
MaterialId = x.MaterialId,
MaterialName = x.MaterialName,
MaterialDescription = x.MaterialDescription,
MaterialActive = x.MaterialActive,
MaterialUsageType = x.MaterialUsageType,
AddDate = x.AddDate,
AddBy = x.AddBy,
ModDate = x.ModDate,
ModBy = x.ModBy
}).ToTable("tblMaterial");
materialConfiguration.HasProperty(x => x.MaterialId).IsIdentity(KeyGenerator.Autoinc);
}
}
}
Repository.cs
namespace OpenAccess
{
public class Repository : IRepository
{
#region private variables
private static OpenAccessContext dat = null;
#endregion private varibles
#region public constructor
/// <summary>
/// Constructor
/// </summary>
public Repository()
{
if (dat == null)
{
dat = new OpenAccessContext();
}
}
#endregion public constructor
#region Material (tblMaterials)
public int CreateMaterial(tblMaterial itm)
{
try
{
dat.Add(itm);
dat.SaveChanges();
return itm.MaterialId;
}
catch (Exception)
{
return 0;
}
}
}
}
tblMaterial.cs
namespace OpenAccess
{
public class tblMaterial
{
public int MaterialId { get; set; }
public string MaterialName { get; set; }
public string MaterialDescription { get; set; }
public bool MaterialActive { get; set; }
public int MaterialUsageType { get; set; }
public DateTime? AddDate { get; set; }
public string AddBy { get; set; }
public DateTime? ModDate { get; set; }
public string ModBy { get; set; }
}
}
In the case of tblContacts, I would suggest to you the following workflow for extending the model:
Add a new class file that will hold the definition of the tblContact POCO class. In this class add properties that will correspond to the columns of the table. The types of the properties should logically match the datatypes of the table columns.
In the OpenAccessMetadataSource class, add a new MappingConfiguration<tblContact> for the tblContact class and using explicit mapping provide the mapping details that logically connect the tblContact class with the tblContacts table. Make sure to add both the existing and the new mapping configurations to the configurations list.
Expose the newly added class through an IQueryable<tblContact> property in the context. This property will allow you to compose LINQ queries against the tblContacts table.
Regarding the Repository class, it seems like it is related to the custom logic of the application. It surely is not a file generated by Data Access. Therefore, you need to discuss it in your team.
I also strongly advise you against using OpenAccess in the namespaces of your application. This is known to interfere with the Data Access' namespaces during build time and at some point it causes runtime errors.
I hope this helps.

Migrations.cs is not created successfully

I am creating new module called "sms" in orchard cms using webmatrix. I create it successfully but when i generate "migrateions.cs", it doesn't generated successfully.
my sms.cs class in Model is given below
using System.Collections.Generic;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Records;
namespace SMSs.Model{
public class smsrecord:ContentPartRecord
{
public virtual int ID{get;set;}
public virtual string Name{get;set;}
public virtual char is_deleted{get;set;}
}
public class smspart:ContentPart<smsrecord>
{
[Required]
public int ID
{
get{return ID=Record.ID;}
set{Record.ID=value;}
}
public string Name
{
get{return Name=Record.Name;}
set{Record.Name=value;}
}
public char is_deleted
{
get{return is_deleted=Record.is_deleted;}
set{Record.is_deleted=value;}
}
}
and the generated Migrations.cs class is as follow
using System;
using System.Collections.Generic;
using System.Data;
using Orchard.ContentManagement.Drivers;
using Orchard.ContentManagement.MetaData;
using Orchard.ContentManagement.MetaData.Builders;
using Orchard.Core.Contents.Extensions;
using Orchard.Data.Migration;
namespace sms {
public class Migrations : DataMigrationImpl {
public int Create() {
return 1;
}
}
}
the "migrations.cs" is not generated successfully why?? Please help
Class itself is generated properly, although it lacks code for creating appropriate tables because you didn't adhere to naming conventions for your record class.
Data migration code generation requires you to follow several conventions in order for it to work properly. I.e.:
Namespace of a record must end with .Models or .Records
There has to exist a public property named Id
All properties have to be virtual (required by NHibernate anyway)
Class cannot be sealed
Class cannot be abstract
Class has to implement IContent or be a subclass of ContentPartRecord
In your case, the namespace (should end with .Models) and incorrect casing of ID (should be Id) are the culprits.

Categories

Resources