Entity Framework 5 code-first not creating database - c#

I'm trying to create a new database using the code first concept of Entity Framework. However when running the code the database isn't created (using the DropCreateDatabaseIfModelChanges setting), though the code is running fine. I'm seeing the following exception when I try to get something from the database.
My project is setup using a separate DataAccess layer with an generic service and repository construction. So all my entities, repository and also database context are in a separate project within the solution.
My global.asax file contains the following piece of code.
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<MyContext>());
This should initialise a new database if it isn't there, right?
My database context class looks like this;
namespace Website.DAL.Model
{
public class MyContext : DbContext
{
public IDbSet<Project> Projects { get; set; }
public IDbSet<Portfolio> Portfolios { get; set; }
/// <summary>
/// The constructor, we provide the connectionstring to be used to it's base class.
/// </summary>
public MyContext()
: base("MyConnectionString")
{
}
static MyContext()
{
try
{
Database.SetInitializer<MyContext>(new DropCreateDatabaseIfModelChanges<MyContext>());
}
catch (Exception)
{
throw;
}
}
/// <summary>
/// This method prevents the plurarization of table names
/// </summary>
/// <param name="modelBuilder"></param>
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<System.Data.Entity.ModelConfiguration.Conventions.PluralizingTableNameConvention>();
}
}
}
I've created this class following several tutorials and articles on the internet. It's all new to me, but as far as I can see everything seems correct so far. So now the two entities I'm using. They're called 'Project' and 'Portfolio'. They look like this;
public class Portfolio
{
[Key]
public Guid Id { get; set; }
public String Name { get; set; }
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public bool IsPublished { get; set; }
public virtual ICollection<Project> Projects { get; set; }
}
And
public class Project
{
[Key]
public Guid Id { get; set; }
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public bool IsPublished { get; set; }
public String Title { get; set; }
}
The database I'm using is running on an external server, it came with the hosting provider I'm using. I've got a SQL Server database up and running and the connection string to the database is in the web.config of the website project. I've already tried removing the database and let the code recreate it, which unfortunately didn't work. Am I missing something obvious here? Or could it be a simple thing as access-rights to the server to create databases?
Note: When I run the Database-Update -Script command to generate SQL code, it seems that the correct SQL statements to create all tables are created.
UPDATE 1:
Okay, thanks to some comments I came a bit further. I've added two properties to my entities to force some changes and I've also created an custom initializer like this;
public class ForceDeleteInitializer : IDatabaseInitializer<MyContext>
{
private readonly IDatabaseInitializer<MyContext> _initializer = new DropCreateDatabaseIfModelChanges<MyContext>();
public ForceDeleteInitializer()
{
//_initializer = new ForceDeleteInitializer();
}
public void InitializeDatabase(MyContext context)
{
//This command is added to prevent open connections. See http://stackoverflow.com/questions/5288996/database-in-use-error-with-entity-framework-4-code-first
context.Database.ExecuteSqlCommand("ALTER DATABASE borloOntwikkel SET SINGLE_USER WITH ROLLBACK IMMEDIATE");
_initializer.InitializeDatabase(context);
}
}
I've also removed the initializer from the constructor of my context, so this mean i've remove this line of code;
Database.SetInitializer<MyContext>(new DropCreateDatabaseIfModelChanges<MyContext>());
After that i've added these three lines to my Global.asax file;
Database.SetInitializer(new ForceDeleteInitializer());
MyContext c = new MyContext();
c.Database.Initialize(true);
When debugging i'm now getting this exception;
This gives me the following information:
InnerException says: The provider didn't return a
ProviderManifestToken
InnerException in the InnerException says: "For this operation an connection to the masterdatabase is required. A connection can't be
made width the 'master'-database because the original connection is
opened and the references has been removed from the connection.
Please provide a non-open connection"
After these action the database is inaccessible, so most likely deleted..
What can possibly be done about this? It's most likely that I can't access the master database because my hostingprovider won't give me the proper access right ofcourse.

Since no other solution came by I decided to change my approach.
I've first created the database myself and made sure the correct SQL user was configured and I had access.
Then I removed the initializer and the code from the Global.asax file. After that I ran the following command in the Package Manager Console (since the layered design I had to select the correct project in the console);
Enable-Migrations
After the migrations where enabled and I made some last minute changes to my entities I ran the command below to scaffold an new migration;
Add-Migration AddSortOrder
After my migrations were created I ran the following command in the console and voila, the database was updated with my entities;
Update-Database -Verbose
To be able to seed the database when running the migration i've overridden the Seed method in my Configuraton.cs class, which was created when enabling the migrations. The final code in this method is like this;
protected override void Seed(MyContext context)
{
// This method will be called after migrating to the latest version.
//Add menu items and pages
if (!context.Menu.Any() && !context.Page.Any())
{
context.Menu.AddOrUpdate(
new Menu()
{
Id = Guid.NewGuid(),
Name = "MainMenu",
Description = "Some menu",
IsDeleted = false,
IsPublished = true,
PublishStart = DateTime.Now,
LastModified = DateTime.Now,
PublishEnd = null,
MenuItems = new List<MenuItem>()
{
new MenuItem()
{
Id = Guid.NewGuid(),
IsDeleted = false,
IsPublished = true,
PublishStart = DateTime.Now,
LastModified = DateTime.Now,
PublishEnd = null,
Name = "Some menuitem",
Page = new Page()
{
Id = Guid.NewGuid(),
ActionName = "Some Action",
ControllerName = "SomeController",
IsPublished = true,
IsDeleted = false,
PublishStart = DateTime.Now,
LastModified = DateTime.Now,
PublishEnd = null,
Title = "Some Page"
}
},
new MenuItem()
{
Id = Guid.NewGuid(),
IsDeleted = false,
IsPublished = true,
PublishStart = DateTime.Now,
LastModified = DateTime.Now,
PublishEnd = null,
Name = "Some MenuItem",
Page = new Page()
{
Id = Guid.NewGuid(),
ActionName = "Some Action",
ControllerName = "SomeController",
IsPublished = true,
IsDeleted = false,
PublishStart = DateTime.Now,
LastModified = DateTime.Now,
PublishEnd = null,
Title = "Some Page"
}
}
}
});
}
if (!context.ComponentType.Any())
{
context.ComponentType.AddOrUpdate(new ComponentType()
{
Id = Guid.NewGuid(),
IsDeleted = false,
IsPublished = true,
LastModified = DateTime.Now,
Name = "MyComponent",
PublishEnd = null,
PublishStart = DateTime.Now
});
}
try
{
// Your code...
// Could also be before try if you know the exception occurs in SaveChanges
context.SaveChanges();
}
catch (DbEntityValidationException e)
{
//foreach (var eve in e.EntityValidationErrors)
//{
// Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
// eve.Entry.Entity.GetType().Name, eve.Entry.State);
// foreach (var ve in eve.ValidationErrors)
// {
// Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"",
// ve.PropertyName, ve.ErrorMessage);
// }
//}
//throw;
var outputLines = new List<string>();
foreach (var eve in e.EntityValidationErrors)
{
outputLines.Add(string.Format(
"{0}: Entity of type \"{1}\" in state \"{2}\" has the following validation errors:",
DateTime.Now, eve.Entry.Entity.GetType().Name, eve.Entry.State));
foreach (var ve in eve.ValidationErrors)
{
outputLines.Add(string.Format(
"- Property: \"{0}\", Error: \"{1}\"",
ve.PropertyName, ve.ErrorMessage));
}
}
System.IO.File.AppendAllLines(#"c:\temp\errors.txt", outputLines);
throw;
}
}
The disadvantage at the moment is that I have to manually migrate with (only) 2 commands in the package manager console. But the same time, the fact that this doesn't happen dynamically is also good because this prevents possibly inwanted changes to my database. Further everything works just perfect.

+1 for the detailed question.
Check that your connection string is pointing at the correct database and add the authorization attributes like this to access your database:
<add name="PatientContext" providerName="System.Data.SqlClient" connectionString="Server=SQLSERVER2; Database=Patients; uid=PatientUser; password=123456; Integrated Security=False;" />

befor intialize it run following code to create your database:
context.Database.CreateIfNotExists();

So First Remove the Db Name from the constructor parameter on context class and provide Db_name on the connection string and then try to rebuild your solution and run the application it will create Database for you application.
For Example :
I am not passing the Db-Name on constructor parameter
public EmployeeContext()
: base()
{
Database.SetInitializer<EmployeeContext>(new DropCreateDatabaseIfModelChanges<EmployeeContext>());
}
and on the Connection string I am passing the Db-name like below.
<add name="EmployeeContext" connectionString="server=.; database=EFCodeFirstTPHInheritance; uid=sa;Password=crius#123;persistsecurityinfo=True" providerName="System.Data.SqlClient"/>
</connectionStrings>

Related

How to save/update only parent entities without saving its childs entities in EF6 in asp.net mvc?

I am working on a survey application with Asp.Net MVC.
I have a page named Index.cshtml which has a question table and a 'Add New' button.Once button clicked, a popup is opened with jQuery. I am calling a view from controller to fill jQuery dialog named as AddOrEdit.cshtml (child page). I am adding new question and options. Question is a textfield and its options are added in editable table. Once clicked submit button, Submit form event (save or update) is fired. My Question and its Options class has a one-to-many relatonship. EF6 tries to save parent entities with its child entities. But I want to save childs after insertion of parents not the same time. How can I handle this problem.
I am using DB First approach. What is the best practice?
Question.cs
namespace MerinosSurvey.Models
{
using System;
using System.Collections.Generic;
public partial class Questions
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Questions()
{
this.Responses = new HashSet<Responses>();
this.Options = new HashSet<Options>();
}
public int QuestionId { get; set; }
public string QuestionName { get; set; }
public int QuestionTypeId { get; set; }
public System.DateTime CreatedDate { get; set; }
public int CreatedUserId { get; set; }
public bool IsActive { get; set; }
public bool Status { get; set; }
public System.DateTime UpdatedDate { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Responses> Responses { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Options> Options { get; set; }
}
}
Option.cs
namespace MerinosSurvey.Models
{
using System;
using System.Collections.Generic;
public partial class Options
{
public int OptionId { get; set; }
public string OptionName { get; set; }
public int QuestionId { get; set; }
public System.DateTime CreatedDate { get; set; }
public System.DateTime UpdatedDate { get; set; }
public bool IsActive { get; set; }
public bool Status { get; set; }
public virtual Questions Questions { get; set; }
}
}
QuestionController.cs - AddOrEdit Action
[HttpPost]
public ActionResult AddOrEdit(Questions question)
{
if (question != null)
{
using (MerinosSurveyEntities db = new MerinosSurveyEntities())
{
Questions questionComing = db.Questions.FirstOrDefault(x => x.QuestionId == question.QuestionId);
if (questionComing != null)
{
//Update
questionComing.QuestionName = question.QuestionName;
questionComing.Status = true;
questionComing.IsActive = true;
questionComing.UpdatedDate = DateTime.Now;
db.Questions.Attach(questionComing);
db.Entry(questionComing).State = EntityState.Modified;
question.QuestionId = questionComing.QuestionId;
db.SaveChanges();
}
else
{
//New Question
question.Status = true;
question.IsActive = true;
question.UpdatedDate = DateTime.Now;
question.CreatedDate = DateTime.Now;
db.Questions.Attach(question);
db.Entry(question).State = EntityState.Added;
db.SaveChanges();
question.QuestionId = question.QuestionId;
}
List<Options> options = question.Options.ToList();
List<Options> existingOptions = new List<Options>(db.Options.Where(x =>
x.Status && x.IsActive && x.QuestionId == question.QuestionId));
foreach (Options existingOption in existingOptions)
{
Options optionUpdated = options.FirstOrDefault(x => x.OptionId == existingOption.OptionId);
if (optionUpdated != null)
{
//Update
existingOption.UpdatedDate = DateTime.Now;
existingOption.OptionName = optionUpdated.OptionName;
existingOption.IsActive = true;
existingOption.Status = true;
db.Options.Attach(existingOption);
db.Entry(existingOption).State = EntityState.Modified;
db.SaveChanges();
options.RemoveAll(x => x.OptionId == existingOption.OptionId);
}
else
{
//Delete
existingOption.Status = false;
existingOption.UpdatedDate = DateTime.Now;
db.Options.Attach(existingOption);
db.Entry(existingOption).State = EntityState.Modified;
db.SaveChanges();
}
}
foreach (Options optionNew in options)
{
optionNew.IsActive = true;
optionNew.Status = true;
optionNew.CreatedDate = DateTime.Now;
optionNew.UpdatedDate = DateTime.Now;
optionNew.QuestionId = question.QuestionId;
db.Options.Add(optionNew);
db.SaveChanges();
}
return Json(new { success = true, message = "Soru başarılı bir şekilde güncellendi."
},
JsonRequestBehavior.AllowGet);
}
}
return Json(new { success = true, message = "Bir problem oluştu." },
JsonRequestBehavior.AllowGet);
}
Your approach is very deliberate, but prone to problems. With EF, the DbContext acts much like a unit of work and SaveChanges should only ever be called once. With something like a related hierarchy where you have a Question with Options, you update and save the question, but then what happens if there is a problem with saving one of the options? You would be committing changes partially and leaving data in an incomplete, inaccurate state.
It's also a LOT of boilerplate code, some of it such as explicitly setting a tracked entity's state to Modified is completely unnecessary. The operation could be revised and simplified down to something like:
[HttpPost]
public ActionResult AddOrEdit(Questions question)
{
if (question == null) // Assert and fail. Avoids nesting.
return Json(new { success = true, message = "Bir problem oluştu." },
JsonRequestBehavior.AllowGet);
using (MerinosSurveyEntities db = new MerinosSurveyEntities())
{
Questions questionComing = db.Questions.Include(x => x.Options).SingleOrDefault(x => x.QuestionId == question.QuestionId); // Prefetch our options...
if (questionComing != null)
{ //Update
questionComing.QuestionName = question.QuestionName;
questionComing.Status = true;
questionComing.IsActive = true;
questionComing.UpdatedDate = DateTime.Now;
// db.Questions.Attach(questionComing); -- not needed, already tracked
// db.Entry(questionComing).State = EntityState.Modified; - Not needed
// question.QuestionId = questionComing.QuestionId; -- Redundant. The ID matches, we loaded based on it.
// db.SaveChanges(); -- No save yet.
// Handle options here... There are probably optimizations that can be added.
var activeOptionIds = question.Options.Where(x => x.Status && s.IsActive).Select(x => x.OptionId).ToList();
foreach(var option in question.Options.Where(x => activeOptionIds.Contains(x.OptionId))
{
var existingOption = questionComing.Options.SingleOrDefault(x => x.OptionId == option.OptionId);
if(existingOption != null)
{ // Update
existingOption.UpdatedDate = DateTime.Now;
existingOption.OptionName = optionUpdated.OptionName;
existingOption.IsActive = true;
existingOption.Status = true;
}
else
{ // New
questionComing.Options.Add(option); // Provided we trust the data coming in. Otherwise new up an option and copy over values.
}
}
var removedOptions = questionComing.Options.Where(x => !activeOptionIds.Contains(x.OptionId).ToList();
foreach(var option in removedOptions)
{
option.Status = option.IsActive = false;
option.UpdatedDate = DateTime.Now;
}
}
else
{ //New Question
// Dangerous to trust the Question coming in. Better to validate and copy values to a new Question to add...
question.Status = true;
question.IsActive = true;
question.UpdatedDate = question.CreatedDate = DateTime.Now;
// db.Questions.Attach(question); -- Add it...
// db.Entry(question).State = EntityState.Added;
// question.QuestionId = question.QuestionId; -- Does nothing...
db.Questions.Add(question); // This will append all Options as well.
}
// Now, after all changes are in, Save.
db.SaveChanges();
return Json(new { success = true, message = "Soru başarılı bir şekilde güncellendi." },JsonRequestBehavior.AllowGet);
} // end using.
}
This I would further break down into private methods to handle the add vs. update. While this doesn't answer how you can update a parent without it's children, it should demonstrate why you should leverage the capabilities of EF to ensure that children are updated with their parents properly. SaveChanges should only ever be called one time within the lifetime scope of a DbContext so that it ensures that all related changes are either committed or rolled back in the event of a failure. EF manages relationships between entities that it is told to track so you can add an entity with new children. Where you need to be careful is with references, such as if you have an existing QuestionType entity associated with a new question. In these scenarios you always want to load the entity within the DbContext scope and use that reference, not a detached reference coming in because EF will treat that as a new entity resulting in duplicate data or duplicate key constraints being hit. It's generally advisable to not pass entities between client and server to avoid issues like this. Attaching or adding entities coming from the client can expose a system to data tampering if not validated properly, and can lead to issues when it comes to referencing existing data.
For instance if you pass in a new question that has a QuestionType reference of "MultipleChoice" (A lookup table of question types) where that is QuestionType ID #1. If you do something like:
db.Questions.Add(question);
"question" was untracked, and all referenced entities are untracked. If you add it, or attach it as a new entity, those referenced entities will be treated as new entities. This would effectively want to Insert a new QuestionType ID #1, leading to a key violation (row already exists) or would insert a new QuestionType ID #12 for example if the QuestionType was configured for incrementing ID. To get around this:
var existingQuestionType = db.QuestionTypes.Single(x => x.QuestionTypeId == question.QuestionType.QuestionTypeId);
question.QuestionType = existingQuestionType; // Points our question type reference at the existing, tracked reference.
db.Questions.Add(question);
question.QuestionType and existingQuestionType would both have an ID of 1 in this example. The difference is that existingQuestionType is tracked/known by the Context where question.QuestionType was an untracked reference. If we added question without the context knowing about the reference, it would treat it like it would a child record of question and want to insert that too. This is probably one of the biggest things that trips people up with EF references and leads to problems and efforts to get more deliberate with related entities, but takes away a lot of the advantages EF can provide. We point our new question reference at the tracked entity so when EF goes to insert the question, it already knows the QuestionType reference as an existing row and everything works as expected.

Setting private set fields from a post request on backend server

I am making my way through various todo list tutorials while learning react and entity framework. As some background I have made my way though Microsoft's todo list todo tutorial; although I have replaced the front end part of that with my own front end. It was all working fine, until I've tried to extend it and hit the issue I will outline below.
I have updated the EF model to include private set fields for the added benefits (becoming read only after it is initialised etc). This is shown in the code below.
public class TodoItem
{
public long id { get; private set; }
public string title { get; private set; }
public bool IsComplete { get; private set; }
// Define constructor
public TodoItem(long newId, string newTitle)
{
id = newId;
title = newTitle;
IsComplete = false;
}
public void ToggleComplete()
{
IsComplete = !IsComplete;
}
}
The post action from the controller is shown below. I have included some debug printouts as these show where the field is already showing the title as null.
I believe this is the section of code I am struggling with and would like to know what mistakes I am making or what the best practices are!
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem item)
{
// returns null if model field set to private
System.Diagnostics.Debug.WriteLine("item title: " + item.title);
// Create new item passing in arguments for constructor
TodoItem newItem = new TodoItem(item.id, item.title);
_context.TodoItems.Add(newItem);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetTodoItem), new { id = newItem.id }, newItem);
}
The frontend method (js) where the post request is made is shown below:
const addTodoMethod = (title) => {
// Create new item
const item = {
title: title,
id: Date.now(),
isComplete: false,
}
// Update state
const newTodos = [...todos, item];
setTodos(newTodos);
// Can use POST requiest to add to db
axios.post('https://localhost:44371/api/todo/',
item)
.then(res=> {
console.log("Added item. Title: ", title);
})
.catch(function (error) {
console.log(error);
})}
I hope I've explained the problem well enough. Let me know if there is anything else needed!
I have updated the EF model to include private set fields for the added benefits (becoming read only after it is initialised etc).
There are two problems in what you did. The first one is that the Models must have a parameter-less constructor, and the second one that the properties must be public, both getter and setter.
The best you can do right now is to stop using your database entity for user input and create a ViewModel class:
public class TodoItemViewModel
{
public long id { get; set; }
public string title { get; set; }
public bool IsComplete { get; set; }
}
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItemViewModel model)
{
var item = new TodoItem(item.id, item.title);
...
}

Ensure that explicitly set primary key values are unique

I am getting following exception on my project:
An exception of type 'System.InvalidOperationException' occurred in
EntityFramework.dll but was not handled in user code
Additional information: Saving or accepting changes failed because of
more than one entity of type 'MyProject.Data.Poco.MyProjectCountry' have the
same primary key value. Ensure that explicitly set primary key values
are unique. Ensure that database-generated primary keys are configured
correctly in the database and in the Entity Framework model. Use the
Entity Designer for Database First/Model First configuration. Use the
'HasDatabaseGeneratedOption" fluent API or
'DatabaseGeneratedAttribute' for Code First configuration.
the error happens at the following line:
using (MyProjectDataContext context = new MyProjectDataContext())
{
MyProjectItemTag existingItemTag = (from p in context.ItemTags.Include(p => p.MyProjectGenre).Include(p => p.MyProjectCountry)
where p.MyProjectUser.UserId == ItemTag.MyProjectUser.UserId &&
p.MyProjectItem.ItemId == MyProjectItem.ItemId
select p).FirstOrDefault();
// new tag
if (existingItemTag == null)
{
existingItemTag = ItemTag;
existingItemTag.MyProjectItem.ItemId = MyProjectItem.ItemId;
}
// existing tag
else
{
existingItemTag.MyProjectItem = new MyProjectItem { ItemId = MyProjectItem.ItemId };
existingItemTag.MyProjectUser = new MyProjectUser { UserId = ItemTag.MyProjectUser.UserId };
}
// updates
existingItemTag.MyProjectCountry = MyProjectCountry;
if (MyProjectCountry != null)
existingItemTag.MyProjectCountry = new MyProjectCountry()
{
MyProjectCountryId = MyProjectCountry.MyProjectCountryId
};
existingItemTag.MyProjectGenre = MyProjectGenre;
context.Entry(existingItemTag.MyProjectItem).State = EntityState.Unchanged;
context.Entry(existingItemTag.MyProjectUser).State = EntityState.Unchanged;
context.Entry(existingItemTag.MyProjectCountry).State = EntityState.Unchanged;
context.Entry(existingItemTag.MyProjectGenre).State = EntityState.Unchanged;
if (existingItemTag.MyProjectCountry != null)
{
context.Entry(existingItemTag.MyProjectCountry).State = EntityState.Unchanged;
}
// db
context.ItemTags.AddOrUpdate(existingItemTag);
context.SaveChanges();
return existingItemTag.ItemTagId;
}
context.Entry(existingItemTag.MyProjectCountry).State = EntityState.Unchanged;
My Class:
public class MyProjectItemTag
{
public int ItemTagId { get; set; }
public MyProjectUser MyProjectUser { get; set; }
public MyProjectItem MyProjectItem { get; set; }
public MyProjectCountry MyProjectCountry { get; set; }
public MyProjectGenre MyProjectGenre { get; set; }
public MyProjectMood MyProjectMood { get; set; }
public MyProjectItemTag()
{
}
public MyProjectItemTag(string userId, string providerContentId)
{
MyProjectUser = new MyProjectUser
{
UserId = userId
};
MyProjectItem = new MyProjectItem
{
ProviderContentId = providerContentId
};
}
}
My Config:
public class MyProjectItemTagConfiguration : EntityTypeConfiguration<MyProjectItemTag>
{
public MyProjectItemTagConfiguration()
{
ToTable("MyProjectItemTags");
HasKey(p => p.ItemTagId);
HasRequired(p => p.MyProjectUser);
HasRequired(p => p.MyProjectItem);
HasOptional(p => p.MyProjectCountry);
}
}
What I am missing here?
This is all you really need to look for:
Additional information: Saving or accepting changes failed because more than one entity of type 'MyProject.Data.Poco.MyProjectCountry' have the same primary key value.
The following code may not necessarily populate the MyProjectCountry.
MyProjectItemTag existingItemTag =
(from p in context.ItemTags
.Include(p => p.MyProjectGenre)
.Include(p => p.MyProjectCountry)
where p.MyProjectUser.UserId == ItemTag.MyProjectUser.UserId
&& p.MyProjectItem.ItemId == MyProjectItem.ItemId
select p).FirstOrDefault();
So you set it to some variable you haven't give us any context too...
existingItemTag.MyProjectCountry = MyProjectCountry;
I'd assume it is not null, so you change it's ID which is a Giant Code Smell...
(Why assign it? after all it's already assigned..)
if (MyProjectCountry != null)
existingItemTag.MyProjectCountry = new MyProjectCountry()
{
MyProjectCountryId = MyProjectCountry.MyProjectCountryId
};
Then you tell EF it hasn't changed?? Another Code Smell.
context.Entry(existingItemTag.MyProjectCountry).State = EntityState.Unchanged;
So what this tells me is that the Context has already downloaded this entity into its Object Cache, but the one you are assigning is not the one in the cache so when I tries to added to the cache, there is a duplicate.
Try to use context.Model.AddORUpdate(model) Method, you need to add using System.Data.Entity.Migrations as well for this method.
Check in the.edmx file StoreGeneratedPattern. If the Id in the database is auto generated, StoreGeneratedPattern has to be Identity. In my case was None. It's not the best practice to edit the edmx file. I personally deleted the table in the edmx file, I created a new one and after that the StoreGeneratedPattern = Identity.

DbContext doesn't release SQLite database

First, these are my intentions:
Create a DbContext on SQLite
Read and write from/to it
Close context
Move the file to another location
Points 1-3 work perfectly. The problem starts when I try to move the database. I get an error stating that:
'The process cannot access the file because it is being used by another process.'
How can I resolve this?
First, I create a context. I have to use it in several methods, and I don't want to create it every time I need it. So I am storing it as a member.
_sqliteContext = new SqlLiteContext(sqliteContextName);
Then I want to access a table called sync and get its latest entry.
var sync = _sqliteContext.Syncs.OrderByDescending(s => s.Date);
_lastSync = sync.Any() ? sync.First().Date : new DateTime(0);
That's it. Then I close the context.
_sqliteContext.Dispose();
And try to move the file.
File.Move(sqliteUploadLocation, sqliteDownloadLocation);
This is where I get the exception.
When I replace the selection with an insert, like the following:
var sync = new Sync { Id = Guid.NewGuid().ToString(), Date = DateTime.Now };
_sqliteContext.Syncs.Add(sync);
_sqliteContext.SaveChanges();
This works, and I can move the database. Any ideas why my selection doesn't release its lock?
Update
// Start synchronisation.
new SyncManager(mssqlString, sqliteUploadLocation).Start();
// Move file from upload to download location.
try
{
File.Move(sqliteUploadLocation, sqliteDownloadLocation);
}
catch (Exception ex)
{
Console.WriteLine("Moving failed!");
Console.WriteLine(ex.Message);
}
public void Start()
{
// Create connection string for the sqlite database.
const string sqliteContextName = "SqLiteContext";
var sqliteConnStringSettings = new ConnectionStringSettings
{
Name = sqliteContextName,
ConnectionString = "Data Source=" + _sqliteUploadLocation + ";Version=3;BinaryGUID=False;",
ProviderName = "System.Data.SQLite"
};
// Read configuration, delete available connection strings and add ours.
var conf = ConfigurationManager.OpenMachineConfiguration();
var connStrings = conf.ConnectionStrings;
connStrings.ConnectionStrings.Remove(sqliteContextName);
connStrings.ConnectionStrings.Add(sqliteConnStringSettings);
try
{
conf.Save(ConfigurationSaveMode.Minimal);
}
catch (Exception)
{
// Insufficient rights to save.
return;
}
ConfigurationManager.RefreshSection("connectionStrings");
// Create connection to the sqlite database.
_sqliteContext = new SqlLiteContext(sqliteContextName);
// Create connection to the mssql database.
_mssqlContext = new MsSqlContext(_mssqlConnString);
// Read last sync date.
var sync = _sqliteContext.Syncs.OrderByDescending(s => s.Date);
_lastSync = sync.Any() ? sync.First().Date : new DateTime(0);
// Synchronize tables.
//SyncTablePerson();
//SyncTableAddressAllocation();
// Creates an entry for this synchronisation.
CreateSyncEntry();
// Release resources.
_sqliteContext.Dispose();
_mssqlContext.Dispose();
}
private void CreateSyncEntry()
{
var sync = new Sync { Id = Guid.NewGuid().ToString(), Date = DateTime.Now };
_sqliteContext.Syncs.Add(sync);
_sqliteContext.SaveChanges();
}
Update 2
public class SqlLiteContext : Context
{
public DbSet<Sync> Syncs { get; set; }
public SqlLiteContext(string connectionString)
: base(connectionString)
{
Database.SetInitializer(new NoOperationStrategy<SqlLiteContext>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new PersonConfig());
modelBuilder.Configurations.Add(new AddressAllocationConfig());
modelBuilder.Configurations.Add(new AddressConfig());
modelBuilder.Configurations.Add(new SyncConfig());
}
}
public class NoOperationStrategy<T> : IDatabaseInitializer<T> where T : DbContext
{
public void InitializeDatabase(T context)
{
}
}
public abstract class Context : DbContext
{
public DbSet<Person> Persons { get; set; }
public DbSet<AddressAllocation> AddressAllocations { get; set; }
public DbSet<Address> Addresses { get; set; }
protected Context(string connectionString)
: base(connectionString)
{
}
}
Refactoring with using
using (var sqliteContext = new SqlLiteContext(_sqliteContextName))
{
// Read last sync date.
var sync = sqliteContext.Syncs.Select(s => s).OrderByDescending(s => s.Date);
var lastSync = sync.Any() ? sync.First().Date : new DateTime(1900, 1, 1);
using (var mssqlContext = new MsSqlContext(_mssqlConnString))
{
SyncTablePerson(sqliteContext, mssqlContext, lastSync);
SyncTableAddressAllocation(sqliteContext, mssqlContext, lastSync);
// Save server changes.
mssqlContext.SaveChanges();
}
// Creates an entry for this synchronisation.
sqliteContext.Syncs.Add(new Sync { Id = Guid.NewGuid().ToString(), Date = DateTime.Now });
// Save local changes.
sqliteContext.SaveChanges();
}
Two things jump to mind:
Make sure Visual Studio isn't locking the database file. Open Server Explorer and if there is a connection to the file make sure its closed or removed altogether.
It's likely that connection pooling is what is holding the connection open. Disable pooling in your connection string like this:
Data Source=e:\mydb.db;Version=3;Pooling=False;
As Matt pointed out you should really use a using statement rather than calling dispose manually. That way if there is an exception the resources are always released properly.
I found another topic with the same problem. After i refactored my code, i added
GC.Collect();
That removed the file lock and i could move the file.
see: https://stackoverflow.com/a/14151917/2462736

Entity Framework Code First Fluent Api: Adding Indexes to columns

I'm running EF 4.2 CF and want to create indexes on certain columns in my POCO objects.
As an example lets say we have this employee class:
public class Employee
{
public int EmployeeID { get; set; }
public string EmployeeCode { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime HireDate { get; set; }
}
We often do searches for employees by their EmployeeCode and since there are a lot of employees it would be nice to have that indexed for performance reasons.
Can we do this with fluent api somehow? or perhaps data annotations?
I know it is possible to execute sql commands something like this:
context.Database.ExecuteSqlCommand("CREATE INDEX IX_NAME ON ...");
I would very much like to avoid raw SQL like that.
i know this does not exist but looking for something along those lines:
class EmployeeConfiguration : EntityTypeConfiguration<Employee>
{
internal EmployeeConfiguration()
{
this.HasIndex(e => e.EmployeeCode)
.HasIndex(e => e.FirstName)
.HasIndex(e => e.LastName);
}
}
or maybe using System.ComponentModel.DataAnnotations the POCO could look like this (again i know this does not exist):
public class Employee
{
public int EmployeeID { get; set; }
[Indexed]
public string EmployeeCode { get; set; }
[Indexed]
public string FirstName { get; set; }
[Indexed]
public string LastName { get; set; }
public DateTime HireDate { get; set; }
}
Anyone have any ideas on how to do this, or if there are any plans to implement a way to do this, the code first way?
UPDATE: As mentioned in the answer by Robba, this feature is implemented in EF version 6.1
After Migrations was introduced in EF 4.3 you can now add indexes when modifying or creating a table. Here is an excerpt from the EF 4.3 Code-Based Migrations Walkthrough from the ADO.NET team blog
namespace MigrationsCodeDemo.Migrations
{
using System.Data.Entity.Migrations;
public partial class AddPostClass : DbMigration
{
public override void Up()
{
CreateTable(
"Posts",
c => new
{
PostId = c.Int(nullable: false, identity: true),
Title = c.String(maxLength: 200),
Content = c.String(),
BlogId = c.Int(nullable: false),
})
.PrimaryKey(t => t.PostId)
.ForeignKey("Blogs", t => t.BlogId, cascadeDelete: true)
.Index(t => t.BlogId)
.Index(p => p.Title, unique: true);
AddColumn("Blogs", "Rating", c => c.Int(nullable: false, defaultValue: 3));
}
public override void Down()
{
DropIndex("Posts", new[] { "BlogId" });
DropForeignKey("Posts", "BlogId", "Blogs");
DropColumn("Blogs", "Rating");
DropTable("Posts");
}
}
}
This is a nice strongly typed way to add the indexes, which was what i was looking for when i first posted the question.
You could create an attribute called indexed (as you suggested), which is then picked up in a custom initializer.
I created the following attribute:
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
public class IndexAttribute : Attribute
{
public IndexAttribute(bool isUnique = false, bool isClustered = false, SortOrder sortOrder = SortOrder.Ascending)
{
IsUnique = isUnique;
IsClustered = isClustered;
SortOrder = sortOrder == SortOrder.Unspecified ? SortOrder.Ascending : sortOrder;
}
public bool IsUnique { get; private set; }
public bool IsClustered { get; private set; }
public SortOrder SortOrder { get; private set; }
//public string Where { get; private set; }
}
I then created a custom initializer which got a list of the table names created for the entities in my context. I have two base classes which all my entities inherit, so I did the following to get the table names:
var baseEF = typeof (BaseEFEntity);
var baseLink = typeof (BaseLinkTable);
var types =
AppDomain.CurrentDomain.GetAssemblies().ToList().SelectMany(s => s.GetTypes()).Where(
baseEF.IsAssignableFrom).Union(AppDomain.CurrentDomain.GetAssemblies().ToList().SelectMany(
s => s.GetTypes()).Where(
baseLink.IsAssignableFrom));
var sqlScript = context.ObjectContext.CreateDatabaseScript();
foreach (var type in types)
{
var table = (TableAttribute) type.GetCustomAttributes(typeof (TableAttribute), true).FirstOrDefault();
var tableName = (table != null ? table.Name : null) ?? Pluralizer.Pluralize(type.Name);
I then found all the properties on each entity that have this attribute and then execute a SQL command to generate the index on each property. Sweet!
//Check that a table exists
if (sqlScript.ToLower().Contains(string.Format(CREATETABLELOOKUP, tableName.ToLower())))
{
//indexes
var indexAttrib = typeof (IndexAttribute);
properties = type.GetProperties().Where(prop => Attribute.IsDefined(prop, indexAttrib));
foreach (var property in properties)
{
var attributes = property.GetCustomAttributes(indexAttrib, true).ToList();
foreach (IndexAttribute index in attributes)
{
var indexName = string.Format(INDEXNAMEFORMAT, tableName, property.Name,
attributes.Count > 1
? UNDERSCORE + (attributes.IndexOf(index) + 1)
: string.Empty);
try
{
context.ObjectContext.ExecuteStoreCommand(
string.Format(INDEX_STRING, indexName,
tableName,
property.Name,
index.IsUnique ? UNIQUE : string.Empty,
index.IsClustered ? CLUSTERED : NONCLUSTERED,
index.SortOrder == SortOrder.Ascending ? ASC : DESC));
}
catch (Exception)
{
}
}
}
I even went on to add class based indexes (which could have multiple columns) , unique constraints and default constraints all in the same way. Whats also really nice is that if you put these attributes on an inherited class the index or constraint gets applied to all the classes (tables) that inherit it.
BTW the pluralizer helper contains the following:
public static class Pluralizer
{
private static object _pluralizer;
private static MethodInfo _pluralizationMethod;
public static string Pluralize(string word)
{
CreatePluralizer();
return (string) _pluralizationMethod.Invoke(_pluralizer, new object[] {word});
}
public static void CreatePluralizer()
{
if (_pluralizer == null)
{
var aseembly = typeof (DbContext).Assembly;
var type =
aseembly.GetType(
"System.Data.Entity.ModelConfiguration.Design.PluralizationServices.EnglishPluralizationService");
_pluralizer = Activator.CreateInstance(type, true);
_pluralizationMethod = _pluralizer.GetType().GetMethod("Pluralize");
}
}
}
To build on frozen's response, you can hand code it into a migration yourself.
First, go to the Package Manager Console and create a new migration with add-migration, then give it a name. A blank migration will appear. Stick this in:
public override void Up()
{
CreateIndex("TableName", "ColumnName");
}
public override void Down()
{
DropIndex("TableName",new[] {"ColumnName"});
}
Note that if you're using a string field it needs to be capped to a length of 450 chars as well.
I've also looked into this recently and found no other way, so I settled with creating indexes when seeding the database:
public class MyDBInitializer : DropCreateDatabaseIfModelChanges<MyContext>
{
private MyContext _Context;
protected override void Seed(MyContext context)
{
base.Seed(context);
_Context = context;
// We create database indexes
CreateIndex("FieldName", typeof(ClassName));
context.SaveChanges();
}
private void CreateIndex(string field, Type table)
{
_Context.Database.ExecuteSqlCommand(String.Format("CREATE INDEX IX_{0} ON {1} ({0})", field, table.Name));
}
}
Note that in Entity Framework 6.1 (currently in beta) will support the IndexAttribute to annotate the index properties which will automatically result in a (unique) index in your Code First Migrations.
For anyone using Entity Framework 6.1+, you can do the following with fluent api:
modelBuilder
.Entity<Department>()
.Property(t => t.Name)
.HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute()));
Read more in the documentation.
Well i found a solution online and adapted it to fit my needs here it is:
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
public class IndexAttribute : Attribute
{
public IndexAttribute(string name, bool unique = false)
{
this.Name = name;
this.IsUnique = unique;
}
public string Name { get; private set; }
public bool IsUnique { get; private set; }
}
public class IndexInitializer<T> : IDatabaseInitializer<T> where T : DbContext
{
private const string CreateIndexQueryTemplate = "CREATE {unique} INDEX {indexName} ON {tableName} ({columnName});";
public void InitializeDatabase(T context)
{
const BindingFlags PublicInstance = BindingFlags.Public | BindingFlags.Instance;
Dictionary<IndexAttribute, List<string>> indexes = new Dictionary<IndexAttribute, List<string>>();
string query = string.Empty;
foreach (var dataSetProperty in typeof(T).GetProperties(PublicInstance).Where(p => p.PropertyType.Name == typeof(DbSet<>).Name))
{
var entityType = dataSetProperty.PropertyType.GetGenericArguments().Single();
TableAttribute[] tableAttributes = (TableAttribute[])entityType.GetCustomAttributes(typeof(TableAttribute), false);
indexes.Clear();
string tableName = tableAttributes.Length != 0 ? tableAttributes[0].Name : dataSetProperty.Name;
foreach (PropertyInfo property in entityType.GetProperties(PublicInstance))
{
IndexAttribute[] indexAttributes = (IndexAttribute[])property.GetCustomAttributes(typeof(IndexAttribute), false);
NotMappedAttribute[] notMappedAttributes = (NotMappedAttribute[])property.GetCustomAttributes(typeof(NotMappedAttribute), false);
if (indexAttributes.Length > 0 && notMappedAttributes.Length == 0)
{
ColumnAttribute[] columnAttributes = (ColumnAttribute[])property.GetCustomAttributes(typeof(ColumnAttribute), false);
foreach (IndexAttribute indexAttribute in indexAttributes)
{
if (!indexes.ContainsKey(indexAttribute))
{
indexes.Add(indexAttribute, new List<string>());
}
if (property.PropertyType.IsValueType || property.PropertyType == typeof(string))
{
string columnName = columnAttributes.Length != 0 ? columnAttributes[0].Name : property.Name;
indexes[indexAttribute].Add(columnName);
}
else
{
indexes[indexAttribute].Add(property.PropertyType.Name + "_" + GetKeyName(property.PropertyType));
}
}
}
}
foreach (IndexAttribute indexAttribute in indexes.Keys)
{
query += CreateIndexQueryTemplate.Replace("{indexName}", indexAttribute.Name)
.Replace("{tableName}", tableName)
.Replace("{columnName}", string.Join(", ", indexes[indexAttribute].ToArray()))
.Replace("{unique}", indexAttribute.IsUnique ? "UNIQUE" : string.Empty);
}
}
if (context.Database.CreateIfNotExists())
{
context.Database.ExecuteSqlCommand(query);
}
}
private string GetKeyName(Type type)
{
PropertyInfo[] propertyInfos = type.GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Public);
foreach (PropertyInfo propertyInfo in propertyInfos)
{
if (propertyInfo.GetCustomAttribute(typeof(KeyAttribute), true) != null)
return propertyInfo.Name;
}
throw new Exception("No property was found with the attribute Key");
}
}
Then overload OnModelCreating in your dbcontext
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer(new IndexInitializer<MyContext>());
base.OnModelCreating(modelBuilder);
}
Apply the index attribute to your Entity type, with this solution you can have multiple fields in the same index just use the same name and unique.
Extending Tsuushin's answer above to support multiple columns and unique constraints:
private void CreateIndex(RBPContext context, string field, string table, bool unique = false)
{
context.Database.ExecuteSqlCommand(String.Format("CREATE {0}NONCLUSTERED INDEX IX_{1}_{2} ON {1} ({3})",
unique ? "UNIQUE " : "",
table,
field.Replace(",","_"),
field));
}
expanding on Petoj
i modified the CreateIndexQueryTemplate to
private const string CreateIndexQueryTemplate = "IF NOT EXISTS (SELECT name FROM sysindexes WHERE name = '{indexName}') CREATE {unique} INDEX {indexName} ON {tableName} ({columnName});";
and removed the following from OnModelCreating
Database.SetInitializer(new IndexInitializer<MyContext>());
and added the following to Configuration Seeding method
new IndexInitializer<MyContext>().InitializeDatabase(context);
this way the index attributes are run every time you do a update-database.
If you want this feature added to EF then you can vote for it here http://entityframework.codeplex.com/workitem/57
jwsadler's extension of Data Annotations was a nice fit for us. We use Annotations to influence the treatment of a class or property and Fluent API for global changes.
Our annotations cover indexes (unique and not unique) plus default values of getdate() and (1). The code sample shows how we applied it to our situation. All of our classes inherit from one base class. This implementation makes a lot of assumptions because we have a pretty simple model. We're using Entity Framework 6.0.1. Lots of comments have been included.
using System;
using System.Linq;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
namespace YourNameSpace
{
public enum SqlOption
{
Active = 1,
GetDate = 2,
Index = 3,
Unique = 4,
}
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
public class SqlAttribute : Attribute
{
public SqlAttribute(SqlOption selectedOption = SqlOption.Index)
{
this.Option = selectedOption;
}
public SqlOption Option {get; set;}
}
// See enum above, usage examples: [Sql(SqlOption.Unique)] [Sql(SqlOption.Index)] [Sql(SqlOption.GetDate)]
public class SqlInitializer<T> : IDatabaseInitializer<T> where T : DbContext
{
// Create templates for the DDL we want generate
const string INDEX_TEMPLATE = "CREATE NONCLUSTERED INDEX IX_{columnName} ON [dbo].[{tableName}] ([{columnName}]);";
const string UNIQUE_TEMPLATE = "CREATE UNIQUE NONCLUSTERED INDEX UQ_{columnName} ON [dbo].[{tableName}] ([{columnName}]);";
const string GETDATE_TEMPLATE = "ALTER TABLE [dbo].[{tableName}] ADD DEFAULT (getdate()) FOR [{columnName}];";
const string ACTIVE_TEMPLATE = "ALTER TABLE [dbo].[{tableName}] ADD DEFAULT (1) FOR [{columnName}];";
// Called by Database.SetInitializer(new IndexInitializer< MyDBContext>()); in MyDBContext.cs
public void InitializeDatabase(T context)
{
// To be used for the SQL DDL that I generate
string sql = string.Empty;
// All of my classes are derived from my base class, Entity
var baseClass = typeof(Entity);
// Get a list of classes in my model derived from my base class
var modelClasses = AppDomain.CurrentDomain.GetAssemblies().ToList().
SelectMany(s => s.GetTypes()).Where(baseClass.IsAssignableFrom);
// For debugging only - examine the SQL DDL that Entity Framework is generating
// Manipulating this is discouraged.
var generatedDDSQL = ((IObjectContextAdapter)context).ObjectContext.CreateDatabaseScript();
// Define which Annotation Attribute we care about (this class!)
var annotationAttribute = typeof(SqlAttribute);
// Generate a list of concrete classes in my model derived from
// Entity class since we follow Table Per Concrete Class (TPC).
var concreteClasses = from modelClass in modelClasses
where !modelClass.IsAbstract
select modelClass;
// Iterate through my model's concrete classes (will be mapped to tables)
foreach (var concreteClass in concreteClasses)
{
// Calculate the table name - could get the table name from list of DbContext's properties
// to be more correct (but this is sufficient in my case)
var tableName = concreteClass.Name + "s";
// Get concrete class's properties that have this annotation
var propertiesWithAnnotations = concreteClass.GetProperties().Where(prop => Attribute.IsDefined(prop, annotationAttribute));
foreach (var annotatedProperty in propertiesWithAnnotations)
{
var columnName = annotatedProperty.Name;
var annotationProperties = annotatedProperty.GetCustomAttributes(annotationAttribute, true).ToList();
foreach (SqlAttribute annotationProperty in annotationProperties)
{
// Generate the appropriate SQL DLL based on the attribute selected
switch (annotationProperty.Option)
{
case SqlOption.Active: // Default value of true plus an index (for my case)
sql += ACTIVE_TEMPLATE.Replace("{tableName}", tableName).Replace("{columnName}", columnName);
sql += INDEX_TEMPLATE.Replace("{tableName}", tableName).Replace("{columnName}", columnName);
break;
case SqlOption.GetDate: // GetDate plus an index (for my case)
sql += GETDATE_TEMPLATE.Replace("{tableName}", tableName).Replace("{columnName}", columnName);
sql += INDEX_TEMPLATE.Replace("{tableName}", tableName).Replace("{columnName}", columnName);
break;
case SqlOption.Index: // Default for empty annotations for example [Sql()]
sql += INDEX_TEMPLATE.Replace("{tableName}", tableName).Replace("{columnName}", columnName);
break;
case SqlOption.Unique:
sql += UNIQUE_TEMPLATE.Replace("{tableName}", tableName).Replace("{columnName}", columnName);
break;
} // switch
} // foreach annotationProperty
} // foreach annotatedProperty
} // foreach concreteClass
// Would have been better not to go through all the work of generating the SQL
// if we weren't going to use it, but putting it here makes it easier to follow.
if (context.Database.CreateIfNotExists())
context.Database.ExecuteSqlCommand(sql);
} // InitializeDatabase
} // SqlInitializer
} // Namespace
Here's our context:
using System;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
namespace YourNameSpace
{
public class MyDBContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Only including my concrete classes here as we're following Table Per Concrete Class (TPC)
public virtual DbSet<Attendance> Attendances { get; set; }
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Location> Locations { get; set; }
public virtual DbSet<PaymentMethod> PaymentMethods { get; set; }
public virtual DbSet<Purchase> Purchases { get; set; }
public virtual DbSet<Student> Students { get; set; }
public virtual DbSet<Teacher> Teachers { get; set; }
// Process the SQL Annotations
Database.SetInitializer(new SqlInitializer<MyDBContext>());
base.OnModelCreating(modelBuilder);
// Change all datetime columns to datetime2
modelBuilder.Properties<DateTime>().Configure(c => c.HasColumnType("datetime2"));
// Turn off cascading deletes
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
}
}
}
To build further on all these great responses, we added the following code to enable the Index attribute to be picked up from an associated metadata type. For the full details please see my blog post, but in summary here are the details.
Metadata types are used like this:
[MetadataType(typeof(UserAccountAnnotations))]
public partial class UserAccount : IDomainEntity
{
[Key]
public int Id { get; set; } // Unique ID
sealed class UserAccountAnnotations
{
[Index("IX_UserName", unique: true)]
public string UserName { get; set; }
}
}
In this example the metadata type is a nested class, but it doesn't have to be, it can be any type. Property matching is done by name only, so the metadata type just has to have a property of the same name, and any data annotations applied to that should then be applied to the associated entity class. This didn't work in the original solution because it doesn't check for the associated metadata type. We plumbed in the following helper method:
/// <summary>
/// Gets the index attributes on the specified property and the same property on any associated metadata type.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>IEnumerable{IndexAttribute}.</returns>
IEnumerable<IndexAttribute> GetIndexAttributes(PropertyInfo property)
{
Type entityType = property.DeclaringType;
var indexAttributes = (IndexAttribute[])property.GetCustomAttributes(typeof(IndexAttribute), false);
var metadataAttribute =
entityType.GetCustomAttribute(typeof(MetadataTypeAttribute)) as MetadataTypeAttribute;
if (metadataAttribute == null)
return indexAttributes; // No metadata type
Type associatedMetadataType = metadataAttribute.MetadataClassType;
PropertyInfo associatedProperty = associatedMetadataType.GetProperty(property.Name);
if (associatedProperty == null)
return indexAttributes; // No metadata on the property
var associatedIndexAttributes =
(IndexAttribute[])associatedProperty.GetCustomAttributes(typeof(IndexAttribute), false);
return indexAttributes.Union(associatedIndexAttributes);
}
For EF7 you can use the hasIndex() method.
We can set clustered and non-clustered index as well.
By default primary key will be clustered . We can change that behavior too.
supplierItemEntity.HasKey(supplierItem => supplierItem.SupplierItemId).ForSqlServerIsClustered(false);
supplierItemEntity.HasIndex(s => new { s.ItemId }).ForSqlServerIsClustered(true);
I discovered a problem with the answer #highace gave - the down migration uses the wrong override for DropIndex. Here is what I did:
To comply with Sql Server's limitation on index columns (900 bytes) I reduced the size of a couple of fields in my model
I added the migration using Add-Migration "Add Unique Indexes"
I manually added the CreateIndex and DropIndex methods to the migration. I used the override that takes the index name for the single column index. I used the override that takes an array of column names where the index spans more than one column
And here is the code with examples of both overrides of each method:
public partial class AddUniqueIndexes : DbMigration
{
public override void Up()
{
//Sql Server limits indexes to 900 bytes,
//so we need to ensure cumulative field sizes do not exceed this
//otherwise inserts and updates could be prevented
//http://www.sqlteam.com/article/included-columns-sql-server-2005
AlterColumn("dbo.Answers",
"Text",
c => c.String(nullable: false, maxLength: 400));
AlterColumn("dbo.ConstructionTypes",
"Name",
c => c.String(nullable: false, maxLength: 300));
//[IX_Text] is the name that Entity Framework would use by default
// even if it wasn't specified here
CreateIndex("dbo.Answers",
"Text",
unique: true,
name: "IX_Text");
//Default name is [IX_Name_OrganisationID]
CreateIndex("dbo.ConstructionTypes",
new string[] { "Name", "OrganisationID" },
unique: true);
}
public override void Down()
{
//Drop Indexes before altering fields
//(otherwise it will fail because of dependencies)
//Example of dropping an index based on its name
DropIndex("dbo.Answers", "IX_Text");
//Example of dropping an index based on the columns it targets
DropIndex("dbo.ConstructionTypes",
new string[] { "Name", "OrganisationID" });
AlterColumn("dbo.ConstructionTypes",
"Name",
c => c.String(nullable: false));
AlterColumn("dbo.Answers",
"Text",
c => c.String(nullable: false, maxLength: 500));
}
You can specify index in ModelBuilder
modelBuilder
.Entity<UserSalary>(builder =>
{
builder.HasNoKey();
builder.HasIndex("UserId").IsUnique(false);
builder.ToTable("UserSalary");
});

Categories