I am using code first approach to connect with database and tables but due to some issue enable/add migration command is not creating my tables so I created tables manually. Th application build successfully that means I assume the objDbContext get my table. The name of Table is Task in database.
Below is my code
eDbContext objDbContext = new eDbContext ();
public List<TaskDetail> GetTasks(long eventId)
{
List<TaskDetail> listTask = new List<TaskDetail>();
try {
listTask = (from task in objDbContext.Tasks
where task.EventId==eventId
select new TaskDetail
{
Id = task.Id,
Title = task.Title,
Description = task.Description,
StartDate = task.StartDate,
EndDate = task.EndDate
}
).ToList();
}
catch(Exception ex) {
throw ex;
}
return listTask;
}
Below is database context
public class eDbContext : DbContext
{
public DbSet<Task> Tasks { get; set; }
}
If you have similar problem (plural table names) for other entities, then you should remove PluralizingTableNameConvention (by default EF generates plural table names from entity type names). Add this code to your DbContext class:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
base.OnModelCreating(modelBuilder);
}
If other tables have plural names, then you should just fix mapping for Task entity as #Valkyriee suggested.
Your DbContext Class should look like this:
public class eDbContext : DbContext
{
public IebContext()
: base("name=ConnectionStringName")
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<eDbContext, Migrations.Configuration>("CatalogName"));
}
public DbSet<Task> Tasks{ get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new TaskMap());
}
}
For your Migration you can create a new class like:
internal sealed class Configuration : DbMigrationsConfiguration<eDbContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
//know this might loss data while its true.
AutomaticMigrationDataLossAllowed = true;
ContextKey = "Path to your DbContext Class";
}
protected override void Seed(eDbContext 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" }
// );
//
}
}
Now using this approach you can create your tables with EF code-first and change them later on. Note that I've added a Map Class for Tasks which means i am using fluent api for Mapping my entity:
public class TaskMap : EntityTypeConfiguration<Task>
{
public TaskMap ()
{
ToTable("Tasks");
HasKey(x => x.Id);
}
}
Related
I'm using entity framework core and I would like to use the same owned type in 2 different classes. This is normally fine however in my case I am getting an error.
I am using a MySql database and the requirement is that all booleans are mapped to a field in the database with column type tinyint(1). To achieve this in my OnModelCreating method I loop through all the properties and if the property is boolean I map it to tinyint(1). However as soon as I use the same owned type in 2 different classes I get the error.
Below I have written a demo program which shows my problem. All you need to recreate this is 2 tables, organisations and contacts. Both with fields id, street and home. To use MySQL I have installed the nuget package MySql.Data.EntityFrameworkCore (v8.0.17). I've run the code in a .net core 2.2 console app.
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace MyDemo
{
class Program
{
static void Main(string[] args)
{
using(var ctx = new MyDbContext())
{
var contact = new Contact
{
Address = new Address
{
Street = "x",
Home = true
}
};
ctx.Contacts.Add(contact);
ctx.SaveChanges();
}
}
}
public class MyDbContext: DbContext
{
public MyDbContext()
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseMySQL("{my connection string}");
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Contact>()
.OwnsOne(p => p.Address,
a =>
{
a.Property(p => p.Street)
.HasColumnName("street")
.HasDefaultValue("");
a.Property(p => p.Home)
.HasColumnName("home")
.HasDefaultValue(false);
});
modelBuilder.Entity<Organisation>()
.OwnsOne(p => p.Address,
a =>
{
a.Property(p => p.Street)
.HasColumnName("street")
.HasDefaultValue("");
a.Property(p => p.Home)
.HasColumnName("home")
.HasDefaultValue(false);
});
var entityTypes = modelBuilder.Model.GetEntityTypes()
.ToList();
foreach (var entityType in entityTypes)
{
var properties = entityType
.GetProperties()
.ToList();
foreach (var property in properties)
{
if (property.PropertyInfo == null)
{
continue;
}
if (property.PropertyInfo.PropertyType.IsBoolean())
{
modelBuilder.Entity(entityType.ClrType)
.Property(property.Name)
.HasConversion(new BoolToZeroOneConverter<short>())
.HasColumnType("tinyint(1)");
}
}
}
base.OnModelCreating(modelBuilder);
}
public DbSet<Contact>Contacts { get; set; }
public DbSet<Organisation>Organisations { get; set; }
}
public class Contact
{
public int Id { get; set; }
public Address Address { get; set; }
//other contact fields
}
public class Organisation
{
public int Id { get; set; }
public Address Address { get; set; }
//other organisation fields
}
public class Address
{
public string Street { get; set; }
public bool Home{ get; set; }
}
public static class TypeExtensions
{
public static bool IsBoolean(this Type type)
{
Type t = Nullable.GetUnderlyingType(type) ?? type;
return t == typeof(bool);
}
}
}
After running the above code the error message that shows up is System.InvalidOperationException: 'The entity type 'Address' cannot be added to the model because a weak entity type with the same name already exists'. The part of the code that throws the error is this bit
if (property.PropertyInfo.PropertyType.IsBoolean())
{
modelBuilder.Entity(entityType.ClrType)
.Property(property.Name)
.HasConversion(new BoolToZeroOneConverter<short>())
.HasColumnType("tinyint(1)");
}
How can I change my code so that the OnModelCreating method runs without error so that the contact record is saved correctly to the database?
Update (EF Core 3.x):
Still no public way to get EntityTypeBuilder, but at least the constructor argument has been modified to be IMutableEntityType type, so only
using Microsoft.EntityFrameworkCore.Metadata.Builders;
is needed, and the corresponding code now is
var entityTypeBuilder = new EntityTypeBuilder(entityType);
Original (EF Core 2.x):
The problem is that the ClrType is not enough to identify the owned entity type, hence modelBuilder.Entity(Type) cannot be used to obtain the EntityTypeBuilder instance needed for fluently configuring the entity properties.
Seems like there is no good public way to do that in EF Core 2.x, so all I can suggest is to use some of the EF Core internals (luckily publicly accessible under the typical internal usage warning).
You'd need the following usings:
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
The first is for EntityTypeBuilder class, the second is for AsEntityType() extension method which gives you access to the internal class implementing the IEntityType, and in particular the Builder property.
The modified code looks like this:
var entityTypes = modelBuilder.Model.GetEntityTypes()
.ToList();
foreach (var entityType in entityTypes)
{
var properties = entityType
.GetProperties()
.ToList();
// (1)
var entityTypeBuilder = new EntityTypeBuilder(entityType.AsEntityType().Builder);
foreach (var property in properties)
{
if (property.PropertyInfo == null)
{
continue;
}
if (property.PropertyInfo.PropertyType.IsBoolean())
{
entityTypeBuilder // (2)
.Property(property.Name)
.HasConversion(new BoolToZeroOneConverter<short>())
.HasColumnType("tinyint(1)");
}
}
}
I have a model class:
public class Work
{
public long Id { get; set; }
[Required]
public string Name { get; set; }
}
I want this Work.Name will be unique, so I define the DbContext:
public class MyDbContext : DbContext
{
public MyDbContext () : base() { }
public MyDbContext (DbContextOptions<MyDbContext > options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Work>(entity =>
entity.HasIndex(e => e.Name).IsUnique()
);
}
public DbSet<Work> Works { get; set; }
}
And I want to test this, so I have a test like this:
[Fact]
public void Post_InsertDuplicateWork_ShouldThrowException()
{
var work = new Work
{
Name = "Test Work"
};
using (var context = new MyDbContext (options))
{
context.Works.Add(work);
context.SaveChanges();
}
using (var context = new MyDbContext (options))
{
context.Works.Add(work);
context.SaveChanges();
}
using (var context = new MyDbContext (options))
{
Assert.Equal(1, context.Works.Count());
}
}
( The option object contains settings for InMemoryDatabase)
I don't really know what to check, but the test failed in the Assert, not in the second SaveChanges(). The database (the context) contains two objects with the same Name.
I went over all the relevant questions, but I did not see anyone answering what I was asking.
As others pointed out InMemory database provider ignore all possible constraints.
My suggestion would be then to use Sqlite provider with "in-memory' feature, which will throw an exception for duplicate unique keys.
public MyDbContext CreateSqliteContext()
{
var connectionString =
new SqliteConnectionStringBuilder { DataSource = ":memory:" }.ToString();
var connection = new SqliteConnection(connectionString);
var options = new DbContextOptionsBuilder<MyDbContext>().UseSqlite(connection);
return new MyDbContext(options);
}
private void Insert(Work work)
{
using (var context = CreateSqliteContext())
{
context.Works.Add(work);
context.SaveChanges();
}
}
[Fact]
public void Post_InsertDuplicateWork_ShouldThrowException()
{
var work1 = new Work { Name = "Test Work" };
var work2 = new Work { Name = "Test Work" };
Insert(work1);
Action saveDuplicate = () => Insert(work2);
saveDuplicate.Should().Throw<DbUpdateException>(); // Pass Ok
}
The test fails because the second SaveChanges() will throw an exception from the database that tells you that you cannot add another item because it already contains an object with that Name.
Unique constraints are not enforced silently. Instead, attempting to add a duplicate value will throw an exception when you try to do it. This is so that you can actually react to it, instead of only noticing it after the fact (when you see that the data you attempted to add is not there).
You can test that by using Assert.Throws:
[Fact]
public void Post_InsertDuplicateWork_ShouldThrowException()
{
var work = new Work
{
Name = "Test Work"
};
using (var context = new MyDbContext (options))
{
context.Works.Add(work);
context.SaveChanges();
}
using (var context = new MyDbContext (options))
{
context.Works.Add(work);
Assert.Throws<Exception>(() => context.SaveChanges());
}
}
You can also specify the exact exception there (I don’t remember on top of my head which exception it exactly is that is thrown there), and you can also assign it to a variable (Assert.Throws() returns the exception) and verify the exception message to make sure that this is the exact exception you expect.
I am trying to learn C# coming from a classic ASP/VBScript background.
Up front (just in case someone can answer without all the following background info and code) - My DbContext interface doesn't allow me to do this:
_dbcontext.Entry(model).State = EntityState.Modified;
It balks at me trying to use the Entry method with the following error:
'MyNamespace.Models.IMyDataContext' does not contain a definition for 'Entry' and no extension method 'Entry' accepting a first argument of type 'MyNamespace.Models.IMyDataContext' could be found (are you missing a using directive or an assembly reference?)
How can I properly define my interface so that it will include the Entry method from the DbContext class?
BACKGROUND
I had someone who (supposedly) knows their stuff help me get the following code setup for connecting to MSSQL or MySQL based on data we retrieve from a common connection info table. The schema in MSSQL and MySQL is identical for the data model.
public interface IMyDataContext
{
DbSet<MyModel> ModelData { get; set; }
}
public class dbMySQL : DbContext, IMyDataContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var table = modelBuilder.Entity<MyModel>().ToTable("tablename");
table.HasKey(t => t.Id);
table.Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
table.Property(t => t.Key);
table.Property(t => t.Value);
base.OnModelCreating(modelBuilder);
}
public dbMySQL(DbConnection existingConnection, boolcontextOwnsConnection) : base(existingConnection, contextOwnsConnection) { }
public DbSet<MyModel> ModelData { get; set; }
}
public class dbMSSQL : DbContext, IMyDataContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var table = modelBuilder.Entity<MyModel>().ToTable("tablename");
table.HasKey(t => t.Id);
table.Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
table.Property(t => t.Key);
table.Property(t => t.Value);
base.OnModelCreating(modelBuilder);
}
public dbMSSQL(string connectionString) : base(connectionString) { }
public DbSet<MyModel> ModelData { get; set; }
}
Using the above code, I have been able to successfully grab connection info from a table and return a DbContext as follows:
private IMyDataContext selectDbProvider(int Id)
{
// Get database connection info
var connInfo = _db.ConnModel.Find(Id);
string dbProvider = connInfo.dbType.ToString();
IMyDataContext _dbd;
if (dbProvider == "MySql.Data.MySqlClient")
{
var connectionStringBuilder = new MySqlConnectionStringBuilder();
connectionStringBuilder.Server = connInfo.dbServer;
connectionStringBuilder.UserID = connInfo.dbUser;
connectionStringBuilder.Password = connInfo.dbPassword;
connectionStringBuilder.Database = connInfo.dbName;
connectionStringBuilder.Port = 3306;
_mysqlconn = new MySqlConnection(connectionStringBuilder.ConnectionString);
_dbd = new dbMySQL(_mysqlconn, false);
}
else
{
var connectionStringBuilder = new SqlConnectionStringBuilder();
connectionStringBuilder.DataSource = connInfo.dbServer;
connectionStringBuilder.UserID = connInfo.dbUser;
connectionStringBuilder.Password = connInfo.dbPassword;
connectionStringBuilder.InitialCatalog = connInfo.dbName;
_dbd = new dbMSSQL(connectionStringBuilder.ConnectionString);
}
return _dbd;
}
Using all of the above, I can successfully access data in either MySQL or MSSQL:
_dbd = selectDbProvider(Id);
model = _dbd.ModelData.ToList();
However, when I try to do an update operation, I get the error message I mentioned at the top. How can I properly define my interface so that it will include the Entry method from the DbContext class?
Add a method to your interface for it.
DbEntityEntry Entry(Object entity)
https://msdn.microsoft.com/en-us/library/gg696238(v=vs.113).aspx
EDIT:
public class dbMyContext : DbContext
{
//snip
public dbMyContext(DbConnection existingConnection, boolcontextOwnsConnection) : base(existingConnection, contextOwnsConnection) { }
public dbMyContext(string connectionString) : base(connectionString) { }
//snip
}
Adjust your selectDbProvider class to use dbMyContext instead of dbMySQL and dbMSSQL.
Now you're using an O/RM properly. :)
I am looking to implement Table-per-Hierarchy using EF6 similar to the instructions found here:
example.
I have an abstract base class of User with the following derived types:
Student
Contact
Instructor
When I examine the database table Users the discriminator column value is (Undefined) when I pass a student object into my Save method below. Instead I would expect the value to be Student. Otherwise my data is saved correctly in both the Users and Students tables.
While troubleshooting the problem I added a UserType enumerator Get property to the classes to ensure that I am casting from User to Student.
In my UserRepository class my Save method is below.
public void Save(User user)
{
if (Exists(user.Id))
UpdateUser(user);
else
{
switch (user.Role)
{
case UserType.Role.Base:
_db.Users.Add(user);
break;
case UserType.Role.Student:
_db.Users.Add(user as Student);
break;
case UserType.Role.Instructor:
_db.Users.Add(user as Instructor);
break;
case UserType.Role.Contact:
_db.Users.Add(user as Contact);
break;
}
}
_db.SaveChanges();
}
Failed Alternative
I've tried code like the following to explicitly create a new Student.
private void MapToStudent(User user)
{
_db.Users.Add(new Student()
{
FirstName = user.FirstName,
LastName = user.LastName,
//...
});
}
Question
I am not downcasting correctly? Or rather what is the proper/preferred way to save subclasses using EF?
User Base Class
public abstract class User
{
public int Id { get; set; }
//...
}
internal class UserNotFound: User
{
public override UserType.Role Role
{
get
{
return UserType.Role.Base;
}
}
}
public class Student : User
{
//...
public override UserType.Role Role
{
get { return UserType.Role.Student; }
}
}
public class Contact : User
{
//...
public override UserType.Role Role
{
get { return UserType.Role.Contact; }
}
}
public class Instructor : User
{
//...
public override UserType.Role Role
{
get { return UserType.Role.Instructor; }
}
}
DatabaseContext Mapping
public class DatabaseContext : Context
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>().ToTable("Students");
modelBuilder.Entity<Contact>().ToTable("Contacts");
modelBuilder.Entity<Instructor>().ToTable("Instructors");
}
}
It appears your mappings are incorrect for TPH. The linked example in your questions shows:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<BillingDetail>()
.Map<BankAccount>(m => m.Requires("BillingDetailType").HasValue("BA"))
.Map<CreditCard>(m => m.Requires("BillingDetailType").HasValue("CC"));
}
which modeled after your question might look like:
modelBuilder.Entity<User>()
.Map<Student>(m => m.Requires("Discriminator").HasValue("STU"))
.Map<Instructor>(m => m.Requires("Discriminator").HasValue("INS"));
I have ready my model on code first EF and I try it on sql express and it works. But I have a problem translating it to a sql server: I don't have the permissions to recreate a database I can only add tables to an empty database.
I already see this answer but when I'm trying to replicate it I have some troubles with the context part:
public class DropCreateDatabaseTables : IDatabaseInitializer<Context> {
#region IDatabaseInitializer<Context> Members
public void InitializeDatabase(Context context)
I already put the reference to System.Data.Entity but that don't work and the Context class not is the referenced on System.Runtime.Remoting.Contexts
There is something wrong in the code? Or is a better solution with the last tools of EF?
EDIT:
Finally was:
DbContext:
public class PeopleContext: DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Adress> Adresses{ get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Add Entity type configuration classes
modelBuilder.Configurations.Add(new PersonConfiguration());
modelBuilder.Configurations.Add(new AdressConfiguration());
}
}
Initializer:
public class DropCreateDatabaseTables : IDatabaseInitializer<PeopleContext>
{
public void InitializeDatabase(PeopleContextContext)
{
bool dbExists;
using (new TransactionScope(TransactionScopeOption.Suppress))
{
dbExists = Context.Database.Exists();
}
if (dbExists)
{
// remove all tables
Context.Database.ExecuteSqlCommand("EXEC sp_MSForEachTable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'");
Context.Database.ExecuteSqlCommand("EXEC sp_MSforeachtable #command1 = \"DROP TABLE ?\"");
// create all tables
var dbCreationScript = ((IObjectContextAdapter)Context).ObjectContext.CreateDatabaseScript();
Context.Database.ExecuteSqlCommand(dbCreationScript);
Context.SaveChanges();
}
else
{
throw new ApplicationException("No database instance");
}
}
}
Call:
class Program
{
static void Main(string[] args)
{
var person= new Person
{
Identifier= "John Doe"
};
Database.SetInitializer(new DropCreateDatabaseTables());
using (var context = new PeopleContext())
{
context.People.Add(person);
context.SaveChanges();
}
}
}
Thanks Lukas Kabrt!
The Context class in the example should be your DbContext class i.e. the class where you specify your DbSet<>s.
Example:
DbContext class
public class DataContext : DbContext {
public DbSet<Customer> Customers { get; set; }
}
DatabaseInitializer
public class DropCreateDatabaseTables : IDatabaseInitializer<DataContext> {
...
}