Opening an SQL CE file at runtime with Entity Framework 4 - entity-framework

I am getting started with Entity Framework 4, and I an creating a demo app as a learning exercise. The app is a simple documentation builder, and it uses a SQL CE store. Each documentation project has its own SQL CE data file, and the user opens one of these files to work on a project.
The EDM is very simple. A documentation project is comprised of a list of subjects, each of which has a title, a description, and zero or more notes. So, my entities are Subject, which contains Title and Text properties, and Note, which has Title and Text properties. There is a one-to-many association from Subject to Note.
I am trying to figure out how to open an SQL CE data file. A data file must match the schema of the SQL CE database created by EF4's Create Database Wizard, and I will implement a New File use case elsewhere in the app to implement that requirement. Right now, I am just trying to get an existing data file open in the app.
I have reproduced my existing 'Open File' code below. I have set it up as a static service class called File Services. The code isn't working quite yet, but there is enough to show what I am trying to do. I am trying to hold the ObjectContext open for entity object updates, disposing it when the file is closed.
So, here is my question: Am I on the right track? What do I need to change to make this code work with EF4? Is there an example of how to do this properly?
Thanks for your help.
My existing code:
public static class FileServices
{
#region Private Fields
// Member variables
private static EntityConnection m_EntityConnection;
private static ObjectContext m_ObjectContext;
#endregion
#region Service Methods
/// <summary>
/// Opens an SQL CE database file.
/// </summary>
/// <param name="filePath">The path to the SQL CE file to open.</param>
/// <param name="viewModel">The main window view model.</param>
public static void OpenSqlCeFile(string filePath, MainWindowViewModel viewModel)
{
// Configure an SQL CE connection string
var sqlCeConnectionString = string.Format("Data Source={0}", filePath);
// Configure an EDM connection string
var builder = new EntityConnectionStringBuilder();
builder.Metadata = "res://*/EF4Model.csdl|res://*/EF4Model.ssdl|res://*/EF4Model.msl";
builder.Provider = "System.Data.SqlServerCe";
builder.ProviderConnectionString = sqlCeConnectionString;
var entityConnectionString = builder.ToString();
// Connect to the model
m_EntityConnection = new EntityConnection(entityConnectionString);
m_EntityConnection.Open();
// Create an object context
m_ObjectContext = new Model1Container();
// Get all Subject data
IQueryable<Subject> subjects = from s in Subjects orderby s.Title select s;
// Set view model data property
viewModel.Subjects = new ObservableCollection<Subject>(subjects);
}
/// <summary>
/// Closes an SQL CE database file.
/// </summary>
public static void CloseSqlCeFile()
{
m_EntityConnection.Close();
m_ObjectContext.Dispose();
}
#endregion
}

Here is the answer. I simplified my code and ran it on simpler EDM model, Disney Characters. Model has two entities, Character and Child, with a 1:* association between Character and Child. Children are character's kids--pretty simple stuff. I wrote the demo as a console app to keep it as simple as possible.
Complete code in Program.cs is as follows:
class Program
{
static void Main(string[] args)
{
/* See http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/8a89a728-6c8d-4734-98cb-11b196ba11fd */
// Configure an SQL CE connection string
var filePath = #"D:\Users\dcveeneman\Documents\Visual Studio 2010\Demos\SqlCeEf4Demo\SqlCeEf4Demo\DisneyChars.sdf";
var sqlCeConnectionString = string.Format("Data Source={0}", filePath);
// Create an EDM connection
var builder = new EntityConnectionStringBuilder();
builder.Metadata = "res://*/DisneyChars.csdl|res://*/DisneyChars.ssdl|res://*/DisneyChars.msl";
builder.Provider = "System.Data.SqlServerCe.3.5";
builder.ProviderConnectionString = sqlCeConnectionString;
var edmConnectionString = builder.ToString();
var edmConnection = new EntityConnection(edmConnectionString);
// Build and query an ObjectContext
using (var context = new DisneyCharsContainer(edmConnection))
{
var chars = context.Characters;
foreach(var character in chars)
{
Console.WriteLine("Character name: {0}", character.Name);
foreach(var child in character.Children)
{
Console.WriteLine("Child name: {0}", child.Name);
}
}
Console.ReadLine();
}
}
}
Link at the top of the code is to a forum thread that I used to write the code.
Here is the walkthrough: First, create a database connection. Since I am using SQL CE, I don't have a connection string builder--the connection string is simply a path, so I don't need one. Then I use an EntityConnectionStringBuilder to build an entity connection string, and then I use that to build an EntityConnection. Finally, I pass the connection to the constructor for my ObjectContext. I can then use the ObjectContext to query the EDM.

Finding / opening a SQL Server CE database is, for some weird reason, hard to do. Make sure you can make any kind of connection to the DB at all before trying to get it to work with the EF.

Related

Entity Framework MigrateDatabaseToLatestVersion giving error

I am attempting to use Entity Framework code based migrations with my web site. I currently have a solution with multiple projects in it. There is a Web API project which I want to initialize the database and another project called the DataLayer project. I have enabled migrations in the DataLayer project and created an initial migration that I am hoping will be used to create the database if it does not exist.
Here is the configuration I got when I enabled migrations
public sealed class Configuration : DbMigrationsConfiguration<Harris.ResidentPortal.DataLayer.ResidentPortalContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(Harris.ResidentPortal.DataLayer.ResidentPortalContext context)
{
// This method will be called after migrating to the latest version.
// You can use the DbSet<T>.AddOrUpdate() helper extension method
// to avoid creating duplicate seed data. E.g.
//
// context.People.AddOrUpdate(
// p => p.FullName,
// new Person { FullName = "Andrew Peters" },
// new Person { FullName = "Brice Lambson" },
// new Person { FullName = "Rowan Miller" }
// );
//
}
}
The only change I made to this after it was created was to change it from internal to public so the WebAPI could see it and use it in it's databaseinitializer. Below is the code in the code in the Application_Start that I am using to try to initialize the database
Database.SetInitializer(new MigrateDatabaseToLatestVersion<ResidentPortalContext, Configuration>());
new ResidentPortalUnitOfWork().Context.Users.ToList();
If I run this whether or not a database exists I get the following error
Directory lookup for the file "C:\Users\Dave\Documents\Visual Studio 2012\Projects\ResidentPortal\Harris.ResidentPortal.WebApi\App_Data\Harris.ResidentPortal.DataLayer.ResidentPortalContext.mdf" failed with the operating system error 2(The system cannot find the file specified.).
CREATE DATABASE failed. Some file names listed could not be created. Check related errors.
It seems like it is looking in the totally wrong place for the database. It seems to have something to do with this particular way I am initializing the database because if I change the code to the following.
Database.SetInitializer(new DropCreateDatabaseAlways<ResidentPortalContext>());
new ResidentPortalUnitOfWork().Context.Users.ToList();
The database will get correctly created where it needs to go.
I am at a loss for what is causing it. Could it be that I need to add something else to the configuration class or does it have to do with the fact that all my migration information is in the DataLayer project but I am calling this from the WebAPI project?
I have figured out how to create a dynamic connection string for this process. You need to first add this line into your EntityFramework entry on Web or App.Config instead of the line that gets put there by default.
<defaultConnectionFactory type="<Namespace>.<ConnectionStringFacotry>, <Assembly>"/>
This tells the program you have your own factory that will return a DbConnection. Below is the code I used to make my own factory. Part of this is a hack to get by the fact that a bunch of programmers work on the same set of code but some of us use SQL Express while others use full blown SQL Server. But this will give you an example to go by for what you need.
public sealed class ResidentPortalConnectionStringFactory: IDbConnectionFactory
{
public DbConnection CreateConnection(string nameOrConnectionString)
{
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(ConfigurationManager.ConnectionStrings["PortalDatabase"].ConnectionString);
//save off the original catalog
string originalCatalog = builder.InitialCatalog;
//we're going to connect to the master db in case the database doesn't exist yet
builder.InitialCatalog = "master";
string masterConnectionString = builder.ToString();
//attempt to connect to the master db on the source specified in the config file
using (SqlConnection conn = new SqlConnection(masterConnectionString))
{
try
{
conn.Open();
}
catch
{
//if we can't connect, then append on \SQLEXPRESS to the data source
builder.DataSource = builder.DataSource + "\\SQLEXPRESS";
}
finally
{
conn.Close();
}
}
//set the connection string back to the original database instead of the master db
builder.InitialCatalog = originalCatalog;
DbConnection temp = SqlClientFactory.Instance.CreateConnection();
temp.ConnectionString = builder.ToString();
return temp;
}
}
Once I did that I coudl run this code in my Global.asax with no issues
Database.SetInitializer(new MigrateDatabaseToLatestVersion<ResidentPortalContext, Configuration>());
using (ResidentPortalUnitOfWork temp = new ResidentPortalUnitOfWork())
{
temp.Context.Database.Initialize(true);
}

Entity Framework - Database First without config

I'm developing a class library that deals with an exiting db using EF. I want to avoid the consumer of the class library (and .exe or a web site) to have in the *.config file the Entity connection string. I want the connection string set a run-time.
How do I set the connection string with Database First approach? There is no constructor overload that takes a connection string and when I created one (in a separate partial class) I got an "UnintentionalCodeFirstException".
I have reviewed already the following links:
Is there a way to change connection string in database first?. Its about modifying the connection string in the config file, which I want to avoid, also because it would recycle the process (in the case of a web app)
How can l use Entity Framework without App.config. Not good because it uses ObjectContext and I need the context generated when I imported the database.
There is a constructor on DbContext that takes a DbConnection, and you need to use an EntityConnection object for it:
SqlConnectionStringBuilder sqlBuilder = new SqlConnectionStringBuilder();
// Set the properties for the data source.
sqlBuilder.DataSource = "server name";
sqlBuilder.InitialCatalog = "database name";
sqlBuilder.IntegratedSecurity = true;
// Build the SqlConnection connection string.
string providerString = sqlBuilder.ToString();
var entityBuilder = new EntityConnectionStringBuilder();
// Initialize the EntityConnectionStringBuilder.
//Set the provider name.
entityBuilder.Provider = "System.Data.SqlClient";
// Set the provider-specific connection string.
entityBuilder.ProviderConnectionString = providerString;
// Set the Metadata location.
entityBuilder.Metadata = #"res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl";
using(var context = new YourDbContext(entityBuilder.ToString())){
//do stuff here
}
The important thing to note is the metadata part - "Model1" obviously needs to be replaced for your model name.
Ref: http://msdn.microsoft.com/en-us/library/bb738533.aspx
EDIT 20/02/2013 22:25
So as an addition you'll need to extend the created DbContext class with a partial class that adds a constructor to support the above code, like this:
public partial class YourDbContext
{
public YourDbContext(string connection) : base(connection) {}
}
This class needs to be in the same namespace as the DbContext that is generated by the entity framework wizard.

Unable to persist Entities to SQL CE using entity framework

I am stating out with entity framework. I have created my ADO.NET Entity Model and mapped the entities to a local SQL CE database file (all done via the wizards). I have created a unit test to test the data access and see how things work. The test executes fine and without any exceptions. However, no new row is generated in the database. Please Help!!!
public void TestCreateRelationshipType()
{
using (var c = new TenderModelEntities())
{
IList<RelationshipType> types = c.RelationshipTypes.ToList<RelationshipType>();
int num1 = types.Count();
RelationshipType type = new RelationshipType();
type.Description = "New Client";
c.AddToRelationshipTypes(type);
c.SaveChanges();
IList<RelationshipType> types2 = c.RelationshipTypes.ToList<RelationshipType>();
int num2 = types2.Count();
Assert.AreEqual(num1 + 1, num2);
}
}
New row is added to the database because you call the SaveChanges() function. When you call this on your datacontext, the changes are passed on to the database.
If you don't want to make any changes to the database, just comment out this section like below
// c.SaveChanges();

EF Code First - Recreate Database If Model Changes

I'm currently working on a project which is using EF Code First with POCOs. I have 5 POCOs that so far depends on the POCO "User".
The POCO "User" should refer to my already existing MemberShip table "aspnet_Users" (which I map it to in the OnModelCreating method of the DbContext).
The problem is that I want to take advantage of the "Recreate Database If Model changes" feature as Scott Gu shows at: http://weblogs.asp.net/scottgu/archive/2010/07/16/code-first-development-with-entity-framework-4.aspx - What the feature basically does is to recreate the database as soon as it sees any changes in my POCOs. What I want it to do is to Recreate the database but to somehow NOT delete the whole Database so that aspnet_Users is still alive. However it seems impossible as it either makes a whole new Database or replaces the current one with..
So my question is: Am I doomed to define my database tables by hand, or can I somehow merge my POCOs into my current database and still take use of the feature without wipeing it all?
As of EF Code First in CTP5, this is not possible. Code First will drop and create your database or it does not touch it at all. I think in your case, you should manually create your full database and then try to come up with an object model that matches the DB.
That said, EF team is actively working on the feature that you are looking for: altering the database instead of recreating it:
Code First Database Evolution (aka Migrations)
I was just able to do this in EF 4.1 with the following considerations:
CodeFirst
DropCreateDatabaseAlways
keeping the same connection string and database name
The database is still deleted and recreated - it has to be to for the schema to reflect your model changes -- but your data remains intact.
Here's how: you read your database into your in-memory POCO objects, and then after the POCO objects have successfully made it into memory, you then let EF drop and recreate the database. Here is an example
public class NorthwindDbContextInitializer : DropCreateDatabaseAlways<NorthindDbContext> {
/// <summary>
/// Connection from which to ead the data from, to insert into the new database.
/// Not the same connection instance as the DbContext, but may have the same connection string.
/// </summary>
DbConnection connection;
Dictionary<Tuple<PropertyInfo,Type>, System.Collections.IEnumerable> map;
public NorthwindDbContextInitializer(DbConnection connection, Dictionary<Tuple<PropertyInfo, Type>, System.Collections.IEnumerable> map = null) {
this.connection = connection;
this.map = map ?? ReadDataIntoMemory();
}
//read data into memory BEFORE database is dropped
Dictionary<Tuple<PropertyInfo, Type>, System.Collections.IEnumerable> ReadDataIntoMemory() {
Dictionary<Tuple<PropertyInfo,Type>, System.Collections.IEnumerable> map = new Dictionary<Tuple<PropertyInfo,Type>,System.Collections.IEnumerable>();
switch (connection.State) {
case System.Data.ConnectionState.Closed:
connection.Open();
break;
}
using (this.connection) {
var metaquery = from p in typeof(NorthindDbContext).GetProperties().Where(p => p.PropertyType.IsGenericType)
let elementType = p.PropertyType.GetGenericArguments()[0]
let dbsetType = typeof(DbSet<>).MakeGenericType(elementType)
where dbsetType.IsAssignableFrom(p.PropertyType)
select new Tuple<PropertyInfo, Type>(p, elementType);
foreach (var tuple in metaquery) {
map.Add(tuple, ExecuteReader(tuple));
}
this.connection.Close();
Database.Delete(this.connection);//call explicitly or else if you let the framework do this implicitly, it will complain the connection is in use.
}
return map;
}
protected override void Seed(NorthindDbContext context) {
foreach (var keyvalue in this.map) {
foreach (var obj in (System.Collections.IEnumerable)keyvalue.Value) {
PropertyInfo p = keyvalue.Key.Item1;
dynamic dbset = p.GetValue(context, null);
dbset.Add(((dynamic)obj));
}
}
context.SaveChanges();
base.Seed(context);
}
System.Collections.IEnumerable ExecuteReader(Tuple<PropertyInfo, Type> tuple) {
DbCommand cmd = this.connection.CreateCommand();
cmd.CommandText = string.Format("select * from [dbo].[{0}]", tuple.Item2.Name);
DbDataReader reader = cmd.ExecuteReader();
using (reader) {
ConstructorInfo ctor = typeof(Test.ObjectReader<>).MakeGenericType(tuple.Item2)
.GetConstructors()[0];
ParameterExpression p = Expression.Parameter(typeof(DbDataReader));
LambdaExpression newlambda = Expression.Lambda(Expression.New(ctor, p), p);
System.Collections.IEnumerable objreader = (System.Collections.IEnumerable)newlambda.Compile().DynamicInvoke(reader);
MethodCallExpression toArray = Expression.Call(typeof(Enumerable),
"ToArray",
new Type[] { tuple.Item2 },
Expression.Constant(objreader));
LambdaExpression lambda = Expression.Lambda(toArray, Expression.Parameter(typeof(IEnumerable<>).MakeGenericType(tuple.Item2)));
var array = (System.Collections.IEnumerable)lambda.Compile().DynamicInvoke(new object[] { objreader });
return array;
}
}
}
This example relies on a ObjectReader class which you can find here if you need it.
I wouldn't bother with the blog articles, read the documentation.
Finally, I would still suggest you always back up your database before running the initialization. (e.g. if the Seed method throws an exception, all your data is in memory, so you risk your data being lost once the program terminates.) A model change isn't exactly an afterthought action anyway, so be sure to back your data up.
One thing you might consider is to use a 'disconnected' foreign key. You can leave the ASPNETDB alone and just reference the user in your DB using the User key (guid). You can access the logged in user as follows:
MembershipUser currentUser = Membership.GetUser(User.Identity.Name, true /* userIsOnline */);
And then use the User's key as a FK in your DB:
Guid UserId = (Guid) currentUser.ProviderUserKey ;
This approach decouples your DB with the ASPNETDB and associated provider architecturally. However, operationally, the data will of course be loosely connected since the IDs will be in each DB. Note also there will be no referential constraints, whcih may or may not be an issue for you.

Is there a standard dialog for constructing an ADO.Net connection string (that is redistributable)?

I want to use a standard dialog to solicit user input of an ADO.net connection string. It is trivial to do for the oledb connection string as described here:
MSDN Article on MSDASC.DataLinks().Prompt
I've also found examples that use Microsoft.Data.ConnectionUI.dll and MicrosoftData.ConnectionUI.Dialog.dll from VS (HOWTO: Using the Choose Data Source dialog of Visual Studio 2005 from your own code).
Unfortunately these DLLs are not licensed for redistribution.
Is there a standard dialog for choosing a data source that can be distributed with my application?
#rathkopf, it looks like these DLLs have been authorized for redistribution since Feb 2010:
http://connect.microsoft.com/VisualStudio/feedback/details/423104/redistributable-microsoft-data-connectionui-dll-and-microsoft-data-connectionui-dialog-dll
http://code.msdn.microsoft.com/Connection
The source code for these DLLs is now available: http://blogs.msdn.com/b/vsdata/archive/2010/02/02/data-connection-dialog-source-code-is-released-on-code-gallery.aspx
Also you can do this programmatically using the DataLink Properties:
Add the reference to ADODB.DLL (from .NET reference) and Microsoft OLE DB Service Component 1.0 Type Library from the COM tab in your visual studio reference tab.
using ADODB;
using Microsoft.Win32;
public partial class ConnectionStringStep : Form
{
private const string MSSQL_PROVIDER = "Provider=SQLOLEDB.1";
private const string ORACLE_PROVIDER = "Provider=MSDAORA.1";
private const string MSSQL = "MSSQL";
public ConnectionStringStep()
{
InitializeComponent();
}
private static string DataBaseType()
{
//get the data from some previous screen or some kind of storage
return MyStorage.GetProperty("DATABASE_TYPE") ?? "MSSQL";
}
private void button1_Click(object sender, EventArgs e)
{
var dataBaseType = DataBaseType();
var adodbConnection = new Connection
{
ConnectionString = dataBaseType == MSSQL ? MSSQL_PROVIDER : ORACLE_PROVIDER
};
object connection = (object) adodbConnection;
var dialog = new MSDASC.DataLinks();
dialog.PromptEdit(ref connection);
connectionTextBox.Text = adodbConnection.ConnectionString;
}
}
DataLink Properties Reference
There is now a NuGet package by Microsoft providing this dialog:
DataConnectionDialog.
Sample usage:
var dialog = new DataConnectionDialog();
dialog.DataSources.Add(DataSource.SqlDataSource);
dialog.ConnectionString = connectionString;
if (DataConnectionDialog.Show(dialog) == System.Windows.Forms.DialogResult.OK)
{
connectionString = dialog.ConnectionString;
}
It's related, but I'm now sure how you can embed this behavior inside your application.
Every time I need one, I create an empty text file, changed its file extension to ".udl" and double-click it; when I'm done, I close that application, rename that file back to ".txt" and open with Notepad.
It appears that such a beast does not exist. I've written my own dialog and can include it in projects as needed.
Update:
The source code for these DLLs are now available as per #code4life's answer.