I have problem with initializing data into SQL Server Compact .sdf data file in .NET web application.
I have a data initialization class.
namespace R10491.Models
{
public class SampleData : DropCreateDatabaseAlways<LibraryEntities>
{
protected override void Seed(LibraryEntities context)
{
var categories = new List<Category>
{
new Category{Id=1, Name="Sci-fi"}
};
}
}
}
(for testing purposes I use DropCreateDatabaseAlways instead of DropCreateDatabaseIfModelChanges)
This initializer class I call in the Global.asax.cs file:
protected void Session_Start()
{
System.Data.Entity.Database.SetInitializer(new R10491.Models.SampleData());
}
(again for testing purposes I call it on every session start).
My connection string definition:
<connectionStrings>
<add name="LibraryEntities"
connectionString="Data Source=C:\Users\Administrator\Documents\Visual Studio 2012\Projects\2OBOP3_KU1\R10491\App_Data\R10491_library.sdf;"
providerName="System.Data.SqlServerCe.4.0"/>
</connectionStrings>
But the initialization doesn't work - tables defined in SampleData class are not created nor data are initialized.
Looks like you're forgetting to add the just created Category to the DB table. If you don't add it to the context's table, Entity Framework won't see anything... So you must do something like this:
protected override void Seed(LibraryEntities context)
{
var categories = new List<Category>
{
new Category{Id=1, Name="Sci-fi"}
};
foreach(Category c in categories)
{
context.Categories.Add(c)
}
// Call the Save method in the Context
context.SaveChanges();
}
For the DataSource problem, try this modified connection string:
<add name="LibraryEntities"
connectionString="DataSource=|DataDirectory|R10491_library.sdf"
providerName="System.Data.SqlServerCe.4.0" />
In one of my projects I have this connection string:
<add name="FitnessCenterContext"
connectionString="DataSource=|DataDirectory|FitnessCenter.Model.FitnessCenterContext.sdf"
providerName="System.Data.SqlServerCe.4.0" />
Note above that the database name matches the namespace and Context name.
I also use Application_Start() to call the SetInitializer method in Global.asax.cs file. I see that you're calling it inside Session_Start(). Maybe this is the problem... Change your code to:
protected void Application_Start()
{
System.Data.Entity.Database.SetInitializer(new R10491.Models.SampleData());
}
You can also try calling the Initialize method:
protected void Application_Start()
{
System.Data.Entity.Database.SetInitializer(new R10491.Models.SampleData());
using (var context = new LibraryEntities())
{
context.Database.Initialize(true);
}
}
Related
I want to seed the database with default data but I get an exception thrown.
I added this initialize class in the DAL namespace
public class MyModelInitialise : DropCreateDatabaseIfModelChanges<MyModelContext>
{
protected override void Seed(MyModelContext context)
{
base.Seed(context);
var MyMarks = new List<MyMark>
{
new Mark{ Name="Mark1", Value="250"},
new Mark{ Name="Mark2", Value="350"},
new Mark{ Name="Mark3", Value="450"}
};
Marks.ForEach(bm => context.Marks.Add(bm));
context.SaveChanges();
}
}
I added this initialiser to app startup
protected void Application_Start()
{
Database.SetInitializer<MyModelContext>(new MyModelInitialise());
}
I get the following error while invoking this
Model compatibility cannot be checked because the DbContext instance was not created
using Code First patterns. DbContext instances created from an ObjectContext or using
an EDMX file cannot be checked for compatibility.
Use the IDatabaseInitializer<TContext> interface instead since DropCreateDatabaseIfModelChanges only works with Code-First models:
public class MyModelInitialise : IDatabaseInitializer<MyModelContext>
{
public void InitializeDatabase(MyModelContext context)
{
// Runs everytime so just avoid if the table already has records.
if (context.Marks.Count() == 0)
{
var MyMarks = new List<MyMark>
{
new Mark{ Name="Mark1", Value="250"},
new Mark{ Name="Mark2", Value="350"},
new Mark{ Name="Mark3", Value="450"}
};
Marks.ForEach(bm => context.Marks.Add(bm));
context.SaveChanges();
}
}
}
Leave the Database.SetInitializer exactly the same. You probably should wrap it in a pre-processor directive to ensure it never ends up in production code.
#if DEBUG
Database.SetInitializer<MyModelContext>(new MyModelInitialise());
#endif
Make sure you have public DbSet<MyMark> MyMarks { get; set; } in the MyModelContext class.
As I understand a connection string is attached to one class only. But what if I have many Model classes? Can I use one connection string for multiple classes?
This is a simple version of my UserModel.cs file:
public class UserModel
{
public int Id { get; set; }
public string Email { get; set; }
}
public class UserTable : DbContext
{
public UserModel GetByEmail(string Email)
{
return this.Database.SqlQuery<UserModel>("SELECT * FROM Users WHERE Email=#Email", new SqlParameter("Email", Email)).SingleOrDefault();
}
}
And this is the connection string:
<connectionStrings>
<add name="UserModel"
connectionString="Server=.\SQLEXPRESS;Database=MyDatabase;User Id=MyUser;Password=MyPassword;"
providerName="System.Data.SqlClient" />
</connectionStrings>
Now lets say I want to add a new Model class named DataTable also derived from DbContext as user table is. Do I need a connection string named the same or can I use the already defined one? What is the conventional way of dealing with multiple Model classes and connection strings?
The DbContext class uses the ConnectionString to make the connection to the database.
You normally have multiple model classes exposed by a DbContext.
It is possible to have multiple DbContext objects that use the same connection string value to connect to the database. In this way, you can separate portions of your model into separate contexts if desired (for example, if you are creating separate assemblies that access different tables but provide similar services to the application).
One caveat to note with EF up to at least 5.0, you cannot use the code-first migrations with multiple DbContexts, one will overwrite the other's changes. The solution to this is to create an aggregated DbContext that is only used for the Migrations process.
I've done this in an app that I built. I used the Unity IoC container, and the built a Plugin Interface that allowed me to pass my ConnectionStringName into my separated DbContexts. An example of the plugin in one of the assemblies was:
public class Bootstrapper : IBootstrapper
{
public void Bootstrap(IUnityContainer container, string connectionStringName)
{
container.RegisterType<ISQService, SQService>();
container.RegisterType<ISQEntities, SQEntities>(
new HierarchicalLifetimeManager(), new InjectionConstructor(connectionStringName));
container.RegisterType<IController, SQController>("SQ");
}
}
My global.asax referenced the bootstrapper class below:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
ModelBinders.Binders[typeof(DataTable)] = new DataTableModelBinder();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
Bootstrapper.Initialise();
}
protected void Application_End()
{
Bootstrapper.Dispose();
}
Bootstrapper
public static class Bootstrapper
{
private static IUnityContainer container;
public static void Initialise()
{
container = BuildUnityContainer();
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
}
public static void Dispose()
{
container.Dispose();
}
private static void RegisterPlugins(IUnityContainer theContainer, string wildcard, string connectionStringName)
{
var pluginBootStrappers = from Assembly assembly in wildcard.LoadAssemblies()
from type in assembly.GetExportedTypes()
where typeof(IBootstrapper).IsAssignableFrom(type)
select (IBootstrapper)Activator.CreateInstance(type);
pluginBootStrappers.ToList().ForEach(b => b.Bootstrap(theContainer, connectionStringName));
}
private static IUnityContainer BuildUnityContainer()
{
var theContainer = new UnityContainer();
const string ConnectionStringName = "MyDb";
RegisterPlugins(theContainer, "MyApp.Systems.*.dll", ConnectionStringName);
// Register Application Specific objects
theContainer.RegisterType<IMyEntities, MyEntities>(
new HierarchicalLifetimeManager(),
new InjectionConstructor(ConnectionStringName));
theContainer.RegisterType<IAimaService, AimaService>();
var factory = new UnityControllerFactory(theContainer);
ControllerBuilder.Current.SetControllerFactory(factory);
return theContainer;
}
}
The connection string defines the parameters needed to connect to the DB.
Maybe I think you are talking about or confusing the SQL Query with the connectionstring.
Yes one SQL Query can QUERY more than one table at any given time.
Maybe you could look into the "SQL Query Statement" on google for in depth information.
I have a small MVC application that connects to a single MYSQL database. I had it setup with Ninject to bind the connectionString during the application startup. The code looked like this:
Global.asax.cs:
protected void Application_Start()
{
...
ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory());
}
NinjectControllerFactory.cs:
public class NinjectControllerFactory : DefaultControllerFactory
{
...
private class EriskServices : NinjectModule
{
public override void Load()
{
// Bind all the Repositories
Bind<IRisksRepository>().To<MySql_RisksRepository>()
.WithConstructorArgument("connectionString",
ConfigurationManager.ConnectionStrings["dbcMain"]
.ConnectionString);
}
}
}
Today my requirements have changed and I have to now support multiple databases. I would like to have each database connection string defined in the web.config file, like how it was before. The user selects which database they want to connect to during the application login.
What would be the easiest way to bind my repositories after the login? I'm assuming I would need to code the database binding in the login controller.
I am kind of a newbie to Ninject so any examples would be much appreciated!
As always, Thanks for the time and help!
.
I would probably Bind the repository to a Ninject.Activation.IProvider, and then create your own provider that pulls the connectionString from Session
Bind<IRisksRepository>().ToProvider<SessionConnectionProvider>();
then...
public class SessionConnectionProvider : Ninject.Activation.IProvider
{
#region IProvider Members
public object Create( Ninject.Activation.IContext context )
{
// use however you're accessing session here
var conStr = session.ConnectionString;
return new MySql_RisksRepository( conStr );
}
public Type Type
{
get { return typeof( IRisksRepository ); }
}
#endregion
}
In the past I've stuck common properties, such as the current user, onto ViewData/ViewBag in a global fashion by having all Controllers inherit from a common base controller.
This allowed my to use IoC on the base controller and not just reach out into global shared for such data.
I'm wondering if there is an alternate way of inserting this kind of code into the MVC pipeline?
The best way is using the ActionFilterAttribute. I'll show you how to use it in .Net Core and .Net Framework.
.Net Core 2.1 & 3.1
public class ViewBagActionFilter : ActionFilterAttribute
{
public ViewBagActionFilter(IOptions<Settings> settings){
//DI will inject what you need here
}
public override void OnResultExecuting(ResultExecutingContext context)
{
// for razor pages
if (context.Controller is PageModel)
{
var controller = context.Controller as PageModel;
controller.ViewData.Add("Avatar", $"~/avatar/empty.png");
// or
controller.ViewBag.Avatar = $"~/avatar/empty.png";
//also you have access to the httpcontext & route in controller.HttpContext & controller.RouteData
}
// for Razor Views
if (context.Controller is Controller)
{
var controller = context.Controller as Controller;
controller.ViewData.Add("Avatar", $"~/avatar/empty.png");
// or
controller.ViewBag.Avatar = $"~/avatar/empty.png";
//also you have access to the httpcontext & route in controller.HttpContext & controller.RouteData
}
base.OnResultExecuting(context);
}
}
Then you need to register this in your startup.cs.
.Net Core 3.1
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews(options => {
options.Filters.Add<Components.ViewBagActionFilter>();
});
}
.Net Core 2.1
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Filters.Add<Configs.ViewBagActionFilter>();
});
}
Then you can use it in all views and pages
#ViewData["Avatar"]
#ViewBag.Avatar
.Net Framework (ASP.NET MVC .Net Framework)
public class UserProfilePictureActionFilter : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
filterContext.Controller.ViewBag.IsAuthenticated = MembershipService.IsAuthenticated;
filterContext.Controller.ViewBag.IsAdmin = MembershipService.IsAdmin;
var userProfile = MembershipService.GetCurrentUserProfile();
if (userProfile != null)
{
filterContext.Controller.ViewBag.Avatar = userProfile.Picture;
}
}
}
register your custom class in the global. asax (Application_Start)
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalFilters.Filters.Add(new UserProfilePictureActionFilter(), 0);
}
Then you can use it in all views
#ViewBag.IsAdmin
#ViewBag.IsAuthenticated
#ViewBag.Avatar
Also there is another way
Creating an extension method on HtmlHelper
[Extension()]
public string MyTest(System.Web.Mvc.HtmlHelper htmlHelper)
{
return "This is a test";
}
Then you can use it in all views
#Html.MyTest()
Since ViewBag properties are, by definition, tied to the view presentation and any light view logic that may be necessary, I'd create a base WebViewPage and set the properties on page initialization. It's very similar to the concept of a base controller for repeated logic and common functionality, but for your views:
public abstract class ApplicationViewPage<T> : WebViewPage<T>
{
protected override void InitializePage()
{
SetViewBagDefaultProperties();
base.InitializePage();
}
private void SetViewBagDefaultProperties()
{
ViewBag.GlobalProperty = "MyValue";
}
}
And then in \Views\Web.config, set the pageBaseType property:
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="MyNamespace.ApplicationViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
</namespaces>
</pages>
</system.web.webPages.razor>
Un-tried by me, but you might look at registering your views and then setting the view data during the activation process.
Because views are registered on-the-fly, the registration syntax doesn't help you with connecting to the Activated event, so you'd need to set it up in a Module:
class SetViewBagItemsModule : Module
{
protected override void AttachToComponentRegistration(
IComponentRegistration registration,
IComponentRegistry registry)
{
if (typeof(WebViewPage).IsAssignableFrom(registration.Activator.LimitType))
{
registration.Activated += (s, e) => {
((WebViewPage)e.Instance).ViewBag.Global = "global";
};
}
}
}
This might be one of those "only tool's a hammer"-type suggestions from me; there may be simpler MVC-enabled ways to get at it.
Edit: Alternate, less code approach - just attach to the Controller
public class SetViewBagItemsModule: Module
{
protected override void AttachToComponentRegistration(IComponentRegistry cr,
IComponentRegistration reg)
{
Type limitType = reg.Activator.LimitType;
if (typeof(Controller).IsAssignableFrom(limitType))
{
registration.Activated += (s, e) =>
{
dynamic viewBag = ((Controller)e.Instance).ViewBag;
viewBag.Config = e.Context.Resolve<Config>();
viewBag.Identity = e.Context.Resolve<IIdentity>();
};
}
}
}
Edit 2: Another approach that works directly from the controller registration code:
builder.RegisterControllers(asm)
.OnActivated(e => {
dynamic viewBag = ((Controller)e.Instance).ViewBag;
viewBag.Config = e.Context.Resolve<Config>();
viewBag.Identity = e.Context.Resolve<IIdentity>();
});
Brandon's post is right on the money. As a matter of fact, I would take this a step further and say that you should just add your common objects as properties of the base WebViewPage so you don't have to cast items from the ViewBag in every single View. I do my CurrentUser setup this way.
You could use a custom ActionResult:
public class GlobalView : ActionResult
{
public override void ExecuteResult(ControllerContext context)
{
context.Controller.ViewData["Global"] = "global";
}
}
Or even a ActionFilter:
public class GlobalView : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.Result = new ViewResult() {ViewData = new ViewDataDictionary()};
base.OnActionExecuting(filterContext);
}
}
Had an MVC 2 project open but both techniques still apply with minor changes.
You don't have to mess with actions or change the model, just use a base controller and cast the existing controller from the layout viewcontext.
Create a base controller with the desired common data (title/page/location etc) and action initialization...
public abstract class _BaseController:Controller {
public Int32 MyCommonValue { get; private set; }
protected override void OnActionExecuting(ActionExecutingContext filterContext) {
MyCommonValue = 12345;
base.OnActionExecuting(filterContext);
}
}
Make sure every controller uses the base controller...
public class UserController:_BaseController {...
Cast the existing base controller from the view context in your _Layout.cshml page...
#{
var myController = (_BaseController)ViewContext.Controller;
}
Now you can refer to values in your base controller from your layout page.
#myController.MyCommonValue
If you want compile time checking and intellisense for the properties in your views then the ViewBag isn't the way to go.
Consider a BaseViewModel class and have your other view models inherit from this class, eg:
Base ViewModel
public class BaseViewModel
{
public bool IsAdmin { get; set; }
public BaseViewModel(IUserService userService)
{
IsAdmin = userService.IsAdmin;
}
}
View specific ViewModel
public class WidgetViewModel : BaseViewModel
{
public string WidgetName { get; set;}
}
Now view code can access the property directly in the view
<p>Is Admin: #Model.IsAdmin</p>
I have found the following approach to be the most efficient and gives excellent control utilizing the _ViewStart.chtml file and conditional statements when necessary:
_ViewStart:
#{
Layout = "~/Views/Shared/_Layout.cshtml";
var CurrentView = ViewContext.Controller.ValueProvider.GetValue("controller").RawValue.ToString();
if (CurrentView == "ViewA" || CurrentView == "ViewB" || CurrentView == "ViewC")
{
PageData["Profile"] = db.GetUserAccessProfile();
}
}
ViewA:
#{
var UserProfile= PageData["Profile"] as List<string>;
}
Note:
PageData will work perfectly in Views; however, in the case of a
PartialView, it will need to be passed from the View to
the child Partial.
I implemented the ActionFilterAttribute solution from #Mohammad Karimi. It worked well as I had the same scenario as the OP. I needed to add data to every view. The action filter attribute was executed for every Razor page request, but it was also called for every web API controller request.
Razor Pages offers a page filter attribute to avoid unnecessary execution of the action filter when a web API controller request is made.
Razor Page filters IPageFilter and IAsyncPageFilter allow Razor Pages to run code before and after a Razor Page handler is run.
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Configuration;
namespace MyProject
{
// learn.microsoft.com/en-us/aspnet/core/razor-pages/filter?view=aspnetcore-6.0
// "The following code implements the synchronous IPageFilter"
// Enable the page filter using 'services.AddRazorPages().AddMvcOptions( ... )
// in the 'ConfigureServices()' startup method.
public class ViewDataPageFilter : IPageFilter
{
private readonly IConfiguration _config;
public ViewDataPageFilter(IConfiguration config)
{
_config = config;
}
// "Called after a handler method has been selected,
// but before model binding occurs."
public void OnPageHandlerSelected(PageHandlerSelectedContext context)
{
}
// "Called before the handler method executes,
// after model binding is complete."
public void OnPageHandlerExecuting(PageHandlerExecutingContext context)
{
PageModel page = context.HandlerInstance as PageModel;
if (page == null) { return; }
page.ViewData["cdn"] = _config["cdn:url"];
}
// "Called after the handler method executes,
// before the action result."
public void OnPageHandlerExecuted(PageHandlerExecutedContext context)
{
}
}
}
As per the sample in the filter methods for Razor Pages documentation, the page filter is enabled by:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages()
.AddMvcOptions(options =>
{
options.Filters.Add(new ViewDataPageFilter(Configuration));
});
}
My web app solution consists of 3 projects:
Web App (ASP.NET MVC)
Business Logic Layer (Class Library)
Database Layer (Entity Framework)
I want to use Ninject to manage the lifetime of the DataContext generated by the Entity Framework in the Database Layer.
The Business Logic layer consists of classes which reference repositories (located in the database layer) and my ASP.NET MVC app references the business logic layer's service classes to run code. Each repository creates an instance of the MyDataContext object from the Entity Framework
Repository
public class MyRepository
{
private MyDataContext db;
public MyRepository
{
this.db = new MyDataContext();
}
// methods
}
Business Logic Classes
public class BizLogicClass
{
private MyRepository repos;
public MyRepository
{
this.repos = new MyRepository();
}
// do stuff with the repos
}
Will Ninject handle the lifetime of MyDataContext despite the lengthy dependency chain from the Web App to the Data Layer?
EDIT
I has some problems with it some time ago, but now it seems to work:
Bind<CamelTrapEntities>().To<CamelTrapEntities>().Using<OnePerRequestBehavior>();
Instead of using HttpModule, you can use OnePerRequestBehavior and it will take care of handling context in current request.
EDIT 2
OnePerRequestBehavior needs to be registered in web.config, because it depends on HttpModule too:
In IIS6:
<system.web>
<httpModules>
<add name="OnePerRequestModule" type="Ninject.Core.Behavior.OnePerRequestModule, Ninject.Core"/>
</httpModules>
</system.web>
With IIS7:
<system.webServer>
<modules>
<add name="OnePerRequestModule" type="Ninject.Core.Behavior.OnePerRequestModule, Ninject.Core"/>
</modules>
</system.webServer>
PREVIOUS ANSWER
It is your responsibility to dispose context when it is not needed. Most popular way in ASP.NET is to have one ObjectContext per request. I do it by having HttpModule:
public class CamelTrapEntitiesHttpModule : IHttpModule
{
public void Init(HttpApplication application)
{
application.BeginRequest += ApplicationBeginRequest;
application.EndRequest += ApplicationEndRequest;
}
private void ApplicationEndRequest(object sender, EventArgs e)
{
((CamelTrapEntities) HttpContext.Current.Items[#"CamelTrapEntities"]).Dispose();
}
private static void ApplicationBeginRequest(Object source, EventArgs e)
{
HttpContext.Current.Items[#"CamelTrapEntities"] = new CamelTrapEntities();
}
}
This is injection rule:
Bind<CamelTrapEntities>().ToMethod(c => (CamelTrapEntities) HttpContext.Current.Items[#"CamelTrapEntities"]);
My Repository takes ObjectContext in constructor:
public Repository(CamelTrapEntities ctx)
{
_ctx = ctx;
}
Just want to mention that Autofac with the ASP.Net integration have the request lifetime support built-in. Resolve instances in the RequestContainer and they will be disposed (if implementing IDisposable) at the end of the request.
You should make your classes DI friendly though:
public class MyRepository
{
private MyDataContext db;
public MyRepository(MyDataContext context)
{
this.db = context;
}
// methods
}
public class BizLogicClass
{
private MyRepository repos;
public BizLogicClass(MyRepository repository)
{
this.repos = repository;
}
// do stuff with the repos
}