EF Core - Add new tables to database During Runtime - c#

I have an asp.net core project which needs to be able to support plugins at runtime, and as a consequence, I need to generate database tables based on what has been plugged in. The plugins are each divided in separate projects and they have have their own DbContext class. The plugins to be used are not known during compile-time, only at runtime.
Now in EF Core I thought that there would be a method like "UpdateDatabase" where you can just add tables to the existing database, but I was wrong. Is there a way to accomplish this? I was able to generate a separate database for each of the plugins, but that wasn't quite what I had in mind..I needed all tables in one database.
Here's the code for the "HRContext" plugin:
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.EntityFrameworkCore;
using Plugins.HR.Models.Entities;
namespace Plugins.HR.Contexts
{
public class HrContext : DbContext
{
public HrContext()
{
}
public HrContext(DbContextOptions<HrContext> contextOptions) : base(contextOptions)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("HR");
base.OnModelCreating(modelBuilder);
}
public DbSet<Address> Address { get; set; }
public DbSet<Attendance> Attendance { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Employee> Employees { get; set; }
public DbSet<JobTitle> JobTitles { get; set; }
}
}
Here's another piece of code for the "CoreContext" plugin:
using System;
using System.Collections.Generic;
using System.Text;
using Core.Data.Models;
using Microsoft.EntityFrameworkCore;
namespace Core.Data.Contexts
{
public class CoreContext : DbContext
{
public CoreContext()
{
}
public CoreContext(DbContextOptions<CoreContext> contextOptions) : base(contextOptions)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("Core");
base.OnModelCreating(modelBuilder);
}
public DbSet<Test> Tests { get; set; }
}
}
My ConfigureServices method in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<CoreContext>(options => options.UseSqlServer("Data source = localhost; initial catalog = Company.Core; integrated security = true;"))
.AddDbContext<HrContext>(options => options.UseSqlServer("Data source = localhost; initial catalog = Company.HR; integrated security = true;"));
// Add framework services.
services.AddMvc();
}
If I try to change the connection string to be the same, sooner or later I will get an error that says that the table for one plugin does not exist. I tried "EnsureCreated" but that didn't work too.

I had the same issue. See my solution on GitHub a few days ago, here: EF Core Issue #9238
What you need is something like the following:
// Using an interface, so that we can swap out the implementation to support PG or MySQL, etc if we wish...
public interface IEntityFrameworkHelper
{
void EnsureTables<TContext>(TContext context)
where TContext : DbContext;
}
// Default implementation (SQL Server)
public class SqlEntityFrameworkHelper : IEntityFrameworkHelper
{
public void EnsureTables<TContext>(TContext context)
where TContext : DbContext
{
string script = context.Database.GenerateCreateScript(); // See issue #2943 for this extension method
if (!string.IsNullOrEmpty(script))
{
try
{
var connection = context.Database.GetDbConnection();
bool isConnectionClosed = connection.State == ConnectionState.Closed;
if (isConnectionClosed)
{
connection.Open();
}
var existingTableNames = new List<string>();
using (var command = connection.CreateCommand())
{
command.CommandText = "SELECT table_name from INFORMATION_SCHEMA.TABLES WHERE table_type = 'base table'";
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
existingTableNames.Add(reader.GetString(0).ToLowerInvariant());
}
}
}
var split = script.Split(new[] { "CREATE TABLE " }, StringSplitOptions.RemoveEmptyEntries);
foreach (string sql in split)
{
var tableName = sql.Substring(0, sql.IndexOf("(", StringComparison.OrdinalIgnoreCase));
tableName = tableName.Split('.').Last();
tableName = tableName.Trim().TrimStart('[').TrimEnd(']').ToLowerInvariant();
if (existingTableNames.Contains(tableName))
{
continue;
}
try
{
using (var createCommand = connection.CreateCommand())
{
createCommand.CommandText = "CREATE TABLE " + sql.Substring(0, sql.LastIndexOf(";"));
createCommand.ExecuteNonQuery();
}
}
catch (Exception)
{
// Ignore
}
}
if (isConnectionClosed)
{
connection.Close();
}
}
catch (Exception)
{
// Ignore
}
}
}
}
Then at the end of Startup.Configure(), I resolve an IEntityFrameworkHelper instance and use that with an instance of DbContext to call EnsureTables().
One issue is I need to still account for the parts of the script which are not CREATE TABLE statements. For example, the CREATE INDEX statements.
I requested they give us a clean solution, for example: add a CreateTable<TEntity>() method to IRelationalDatabaseCreator. Not holding my breath for that though...
EDIT
I forgot to post the code for GenerateCreateScript(). See below:
using System.Text;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
public static class DatabaseFacadeExtensions
{
public static string GenerateCreateScript(this DatabaseFacade database)
{
var model = database.GetService<IModel>();
var migrationsModelDiffer = database.GetService<IMigrationsModelDiffer>();
var migrationsSqlGenerator = database.GetService<IMigrationsSqlGenerator>();
var sqlGenerationHelper = database.GetService<ISqlGenerationHelper>();
var operations = migrationsModelDiffer.GetDifferences(null, model);
var commands = migrationsSqlGenerator.Generate(operations, model);
var stringBuilder = new StringBuilder();
foreach (var command in commands)
{
stringBuilder
.Append(command.CommandText)
.AppendLine(sqlGenerationHelper.BatchTerminator);
}
return stringBuilder.ToString();
}
}
It's based on the code found here: EF Core Issue #2943

Related

ElasticSearch: How to fetch data from fields using ElasticClient from Nest 7.11?

I am using ElasticClient.Search function to get the values of the fields.
The issue is :
The code that i make below make the mapping correctly but for searching it returns null values of the fields that was mapped before.
Main.cs
using Nest;
using System;
using System.Linq;
using System.Threading;
namespace DataAccessConsole
{
class Program
{
public static Uri node;
public static ConnectionSettings settings;
public static ElasticClient client;
static void Main(string[] args)
{
{
node = new Uri("http://localhost:9200");
settings = new ConnectionSettings(node).DefaultIndex("getallcommissionspermanentes");
settings.DefaultFieldNameInferrer(p => p);
client = new ElasticClient(settings);
var indexSettings = new IndexSettings();
indexSettings.NumberOfReplicas = 1;
indexSettings.NumberOfShards = 1;
client.Indices.Create("getallcommissionspermanentes", index => index
.Map<GetAllCommissionsPermanentes>(
x => x
.AutoMap<GetAllCommissionsPermanentes>()
));
client.Search<GetAllCommissionsPermanentes>(s => s
.AllIndices()
);
}
}
GetAllCommissionsPermanentes.cs
the table is located in an edmx model of Entityframework and Data came from SQL SERVER Database
public partial class GetAllCommissionsPermanentes
{
public int ID { get; set; }
public string NomAr { get; set; }
public string NomFr { get; set; }
}
if you need more informations just make a comment below.
Thanks
Code is correct but '.All Indices ()' searches in all indexes, results that do not match the model are coming. This code will return more accurate results;
client.Search<GetAllCommissionsPermanentes>(s => s.Index("getallcommissionspermanentes");

Runtime compiling DbContext with EF core, missing reference for Migrator()

I'm building an app that requires a EntityFrameWorkCore DbContext to be created runtime and then migrated, the code below compiles and runs if i don't use the dbContext.Database.Migrate() method, but if i do i get a diagnositcs error about missing a directive/reference.
error CS1061: 'DatabaseFacade' does not contain a definition for 'Migrate' and no accessible extension method 'Migrate' accepting a first argument of type 'DatabaseFacade' could be found (are you missing a using directive or an assembly reference?)
I also don't get any errors if i simply create a file with the code in my project. From what i can tell the "DatabaseFacade" is part of EntityFrameWorkcore.Infrastructure, which should be part if Microsoft.EntityFrameworkCore.
These are the references i'm including in the CSharpCompiler:
"System"
"System.Console"
"System.Runtime"
"System.Private.CoreLib"
"System.Linq"
"System.Data.Common"
"System.Data"
"System.Data.SqlClient"
"System.ComponentModel"
"Microsoft.EntityFrameworkCore"
"Microsoft.EntityFrameworkCore.SqlServer"
"netstandard"
I'm using Microsoft.CodeAnalysis to create a CSharpCompilation and then emit an assembly that i'm calling the main method in during runtime.
I've even tried to call the migrate() method with reflection to bypass intellisence, but GetMethod("Migrate") returns null, so it's obviously not there.
This is the code i am trying to compile and use runtime (Simplyfied):
using System;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using mothership_shared.Data;
using mothership_shared.Enums;
using mothership_shared.Interfaces;
using mothership_shared.Models;
using static mothership_shared.Attributes.PropertyCalculations;
using static mothership_shared.Attributes.PropertyTypes;
using static mothership_shared.Attributes.Settings;
using static mothership_shared.Data.SqlServiceClasses;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace mothership_shared.MigratorProgram
{
public class Program
{
static void Main(string[] args)
{
SqlConnectionSettings sqlConnectionSettings = new SqlConnectionSettings()
{
ServerUrl = args[0],
Catalog = args[1],
User = args[2],
Password = args[3],
};
var dbContext = new ApplicationDbContext(sqlConnectionSettings.AppConnection);
ISql sqlService = new SqlService();
var request = new DeleteMigrationHistory.Request()
{
ConnectionSettings = sqlConnectionSettings,
};
sqlService.DeleteMigrationHistory(request);
dbContext.Database.Migrate();
}
public void Run(string[] args)
{
Main(args);
}
public class ApplicationDbContext : DbContext
{
private readonly string _connectionString;
public ApplicationDbContext(string connectionString)
{
_connectionString = connectionString;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_connectionString);
}
public DbSet<SimpleEntity> SimpleEntity { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var allTypes = Assembly.GetCallingAssembly().GetTypes();
var EntityTypes = allTypes.Where(t => t.BaseType == typeof(BaseEntity));
foreach (var t in EntityTypes)
{
var crud = t.GetCustomAttribute<CRUDAttribute>();
var properties = t.GetProperties();
foreach (var p in properties)
{
IsObjectAttribute otm = p.GetCustomAttribute<IsObjectAttribute>();
if (otm != null)
{
if (crud.ClassType != ClassType.MTM)
{
modelBuilder.Entity(t)
.HasOne(p.PropertyType, p.Name)
.WithMany(otm.WithMany)
.OnDelete(otm.DeleteBehavior);
}
else
{
modelBuilder.Entity(t)
.HasOne(p.PropertyType, p.Name)
.WithMany(otm.WithMany)
.OnDelete(DeleteBehavior.Cascade);
}
};
IsTimeSpanAttribute ts = p.GetCustomAttribute<IsTimeSpanAttribute>();
if (ts != null)
{
modelBuilder.Entity(t)
.Property(p.Name)
.HasConversion(new TimeSpanToTicksConverter());
}
}
}
}
}
[CRUDAttribute(ClassType.Referal)]
[HeaderAttribute("Simple entity", "Simple entitys", 0)]
[IconAttribute("ms-Icon--Document")]
[MenuAttribute("Test menu")]
[TooltipAttribute("This is a model that contains all simple propertytypes")]
public class SimpleEntity : BaseEntity
{
[IsTextAttribute(false)]
[HeaderAttribute("Text", "Texts", 0)]
[PriorityAttribute(1)]
[TooltipAttribute("This is a text property")]
[IconAttribute("ms-Icon--Text")]
[DefaultValueAttribute("This is the defaultvalue")]
public string TextProperty { get; set; }
}
}
}
You need to install the extension Microsoft.EntityFrameworkCore.Tools, because Migrate is a part of it. If it still doesn't work, you can try dotnet restore, it often helps with extension related problems.

Entity Framework 6 set connection string in code

I have a dll that uses the Entity Framework 6 to do some database operations. I'm using a database first approach.
The model and everything concerning the Entity Framework, like the connection string in the App.config, were created via the wizzard in Visual Studio.
So I compiled the dll and put it together with the corresponding .config in the folder where the application using the dll expects it.
Everything works fine until I get to the point where an actual database call is made. There I get the error:
Cannot find connection string for MyDatabaseEntity
The automatically generated connectionstring is, as I said, in the config file of the dll. I cannot change the App.config of the application.
But the application hands over an object that has all the information I need to build the connection string myself.
So I'm looking for a way to set the connection string in the code without relying on a config file.
All the tutorials I find for a database first approach use this method though.
I found a post here that says to simply give the connection string as a parameter when creating the Object like
MyDatabaseEntities = new MyDatabaseEntities(dbConnect);
but ´MyDatabaseEntities´ doesn't have a constructor that takes any parameters
public partial class MyDatabaseEntities : DbContext
{
public MyDatabaseEntities()
: base("name=MyDatabaseEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<MyTable> MyTable { get; set; }
}
How about:
public partial class MyDatabaseEntities : DbContext
{
public MyDatabaseEntities(string connectionString)
: base(connectionString)
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<MyTable> MyTable { get; set; }
}
Then initialize your database like you did before:
string myConnectionString = "...";
MyDatabaseEntities = new MyDatabaseEntities(myConnectionString);
I had the similar issue. My Edmx and App.Config was in a different project. My startup project was different, had 3 different connection strings, we need to choose one on the fly depending on the environment. So couldn't use a fixed connection string. I created a partial class overload of the Context.cs using the same namespace. Following was my default Context.cs;
namespace CW.Repository.DBModel
{
public partial class CWEntities : DbContext
{
public CWEntities()
: base("name=CWEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
...
...
}
}
My partial class overload;
namespace CW.Repository.DBModel
{
public partial class CWEntities : DbContext
{
public CWEntities(string ConnectionString)
: base(ConnectionString)
{
}
}
}
Lastly, as my connection strings were not for EF, I converted them to a EF connection string.
public static string GetEntityConnectionString(string connectionString)
{
var entityBuilder = new EntityConnectionStringBuilder();
// WARNING
// Check app config and set the appropriate DBModel
entityBuilder.Provider = "System.Data.SqlClient";
entityBuilder.ProviderConnectionString = connectionString + ";MultipleActiveResultSets=True;App=EntityFramework;";
entityBuilder.Metadata = #"res://*/DBModel.CWDB.csdl|res://*/DBModel.CWDB.ssdl|res://*/DBModel.CWDB.msl";
return entityBuilder.ToString();
}
Lastly, the calling
var Entity = new CWEntities(CWUtilities.GetEntityConnectionString(ConnectionString));
I got this solution using below code, I can hardcode connection string using C# code without using config file.
public class SingleConnection
{
private SingleConnection() { }
private static SingleConnection _ConsString = null;
private String _String = null;
public static string ConString
{
get
{
if (_ConsString == null)
{
_ConsString = new SingleConnection { _String = SingleConnection.Connect() };
return _ConsString._String;
}
else
return _ConsString._String;
}
}
public static string Connect()
{
//Build an SQL connection string
SqlConnectionStringBuilder sqlString = new SqlConnectionStringBuilder()
{
DataSource = "SIPL35\\SQL2016".ToString(), // Server name
InitialCatalog = "Join8ShopDB", //Database
UserID = "Sa", //Username
Password = "Sa123!##", //Password
};
//Build an Entity Framework connection string
EntityConnectionStringBuilder entityString = new EntityConnectionStringBuilder()
{
Provider = "System.Data.SqlClient",
Metadata = "res://*/ShopModel.csdl|res://*/ShopModel.ssdl|res://*/ShopModel.msl",
ProviderConnectionString = #"data source=SIPL35\SQL2016;initial catalog=Join8ShopDB2;user id=Sa;password=Sa123!##;"// sqlString.ToString()
};
return entityString.ConnectionString;
}
and using DbContext using like this:
Join8ShopDBEntities dbContext = new Join8ShopDBEntities(SingleConnection.ConString);
Thanks a lot . I changed little for Code First EF6.
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.Entity.Core.EntityClient;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Data
{
public class SingleConnection
{
private SingleConnection() { }
private static SingleConnection _ConsString = null;
private String _String = null;
public static string ConString
{
get
{
if (_ConsString == null)
{
_ConsString = new SingleConnection { _String = SingleConnection.Connect() };
return _ConsString._String;
}
else
return _ConsString._String;
}
}
public static string Connect()
{
string conString = ConfigurationManager.ConnectionStrings["YourConnectionStringsName"].ConnectionString;
if (conString.ToLower().StartsWith("metadata="))
{
System.Data.Entity.Core.EntityClient.EntityConnectionStringBuilder efBuilder = new System.Data.Entity.Core.EntityClient.EntityConnectionStringBuilder(conString);
conString = efBuilder.ProviderConnectionString;
}
SqlConnectionStringBuilder cns = new SqlConnectionStringBuilder(conString);
string dataSource = cns.DataSource;
SqlConnectionStringBuilder sqlString = new SqlConnectionStringBuilder()
{
DataSource = cns.DataSource, // Server name
InitialCatalog = cns.InitialCatalog, //Database
UserID = cns.UserID, //Username
Password = cns.Password, //Password,
MultipleActiveResultSets = true,
ApplicationName = "EntityFramework",
};
//Build an Entity Framework connection string
EntityConnectionStringBuilder entityString = new EntityConnectionStringBuilder()
{
Provider = "System.Data.SqlClient",
Metadata = "res://*",
ProviderConnectionString = sqlString.ToString()
};
return entityString.ConnectionString;
}
}
}
You can use singleton patter for it . For example
private YouurDBContext context;
public YouurDBContext Context
{
get
{
if (context==null)
{
context = new YouurDBContext();
}
return context;
}
set { context = value; }
}

NHibernate SchemaExport does not create ntext columns

I create a simple tool which uses SchemaExport to generate database & sql scripts. On a simple entity, one string property Description is expected a ntext column in SQL Server but infact it is nvarchar(255).
Do not sure that which part I was wrong, any advice is appreciated!
Below is my code, just create a console app + add NHibernate nuget package to run.
using System;
using NHibernate.Cfg;
using NHibernate.Dialect;
using NHibernate.Driver;
using NHibernate.Mapping.ByCode;
using NHibernate.Mapping.ByCode.Conformist;
using NHibernate.Tool.hbm2ddl;
namespace ConsoleApplication1
{
public class Item
{
public int Id { get; set; }
public string Description { get; set; }
}
public class ItemMap : ClassMapping<Item>
{
public ItemMap()
{
Id(e => e.Id, m => m.Generator(Generators.Identity));
Property(e => e.Description, m =>
{
m.NotNullable(true);
m.Length(int.MaxValue);
});
}
}
class Program
{
private const string ConnectionString = #"Data Source=.\SQLEXPRESS;Initial Catalog=db01;Integrated Security=True";
static void Main(string[] args)
{
var modelMapper = BuildModelMapper();
var configuration = GetConfiguration();
configuration.AddDeserializedMapping(modelMapper.CompileMappingForAllExplicitlyAddedEntities(), null);
try
{
new SchemaExport(configuration).Execute(false, true, false);
Console.WriteLine("Done");
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
Console.ReadLine();
}
private static ModelMapper BuildModelMapper()
{
var mm = new ModelMapper();
mm.AddMapping(typeof(ItemMap));
return mm;
}
private static Configuration GetConfiguration()
{
var cfg = new Configuration();
cfg.DataBaseIntegration(db =>
{
db.Driver<SqlClientDriver>();
db.Dialect<MsSql2008Dialect>();
db.KeywordsAutoImport = Hbm2DDLKeyWords.AutoQuote;
db.ConnectionString = ConnectionString;
db.LogFormattedSql = true;
db.LogSqlInConsole = true;
db.AutoCommentSql = true;
});
return cfg;
}
}
}
After some further reading, the ntext will be removed in future versions along with text and image https://msdn.microsoft.com/en-us/library/ms187993.aspx
ntext , text, and image data types will be removed in a future version of Microsoft SQL Server. Avoid using these data types in new development work, and plan to modify applications that currently use them. Use nvarchar(max), varchar(max), and varbinary(max) instead.
So this code will work
Property(e => e.Description, m =>
{
m.NotNullable(true);
m.Length(4001); // any value > 4K
});

Cascade Saves with Fluent NHibernate AutoMapping - Old Answer Still Valid?

I want to do exactly what this question asks:
Cascade Saves with Fluent NHibernate AutoMapping
Using Fluent Nhibernate Mappings to turn on "cascade" globally once for all classes and relation types using one call rather than setting it for each mapping individually.
The answer to the earlier question looks great, but I'm afraid that the Fluent Nhibernate API altered its .WithConvention syntax last year and broke the answer... either that or I'm missing something.
I keep getting a bunch of name space not found errors relating to the IOneToOnePart, IManyToOnePart and all their variations:
"The type or namespace name 'IOneToOnePart' could not be found (are you missing a using directive or an assembly reference?)"
I've tried the official example dll's, the RTM dll's and the latest build and none of them seem to make VS 2008 see the required namespace.
The second problem is that I want to use the class with my AutoPersistenceModel
but I'm not sure where to this line:
.ConventionDiscovery.AddFromAssemblyOf()
in my factory creation method.
private static ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(SQLiteConfiguration.Standard.UsingFile(DbFile))
.Mappings(m => m.AutoMappings
.Add(AutoMap.AssemblyOf<Shelf>(type => type.Namespace.EndsWith("Entities"))
.Override<Shelf>(map =>
{
map.HasManyToMany(x => x.Products).Cascade.All();
})
)
)//emd mappings
.ExposeConfiguration(BuildSchema)
.BuildSessionFactory();//finalizes the whole thing to send back.
}
Below is the class and using statements I'm trying
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using FluentNHibernate.Conventions;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;
using FluentNHibernate.Mapping;
namespace TestCode
{
public class CascadeAll : IHasOneConvention, IHasManyConvention, IReferenceConvention
{
public bool Accept(IOneToOnePart target)
{
return true;
}
public void Apply(IOneToOnePart target)
{
target.Cascade.All();
}
public bool Accept(IOneToManyPart target)
{
return true;
}
public void Apply(IOneToManyPart target)
{
target.Cascade.All();
}
public bool Accept(IManyToOnePart target)
{
return true;
}
public void Apply(IManyToOnePart target)
{
target.Cascade.All();
}
}
}
The easiest way I've found to do this for a whole project is to use DefaultCascade:
.Conventions.Add( DefaultCascade.All() );
Go to "The Simplest Conventions" section on the wiki, for this, and a list of others.
Edit:
Here's the list from the Wiki:
Table.Is(x => x.EntityType.Name + "Table")
PrimaryKey.Name.Is(x => "ID")
AutoImport.Never()
DefaultAccess.Field()
DefaultCascade.All()
DefaultLazy.Always()
DynamicInsert.AlwaysTrue()
DynamicUpdate.AlwaysTrue()
OptimisticLock.Is(x => x.Dirty())
Cache.Is(x => x.AsReadOnly())
ForeignKey.EndsWith("ID")
A word of warning - some of the method names in the Wiki may be wrong. I edited the Wiki with what I could verify (i.e. DefaultCascade and DefaultLazy), but can't vouch for the rest. But you should be able to figure out the proper names with Intellisense if the need arises.
Here's a full working example similar to the Getting Started guide https://github.com/jagregory/fluent-nhibernate/wiki/Getting-started
//=====CONSOLE MAIN
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;
using System.IO;
using FluentNHibernate.Automapping;
using App4.Entities;
using System.Diagnostics;
namespace App4
{
class Program
{
static void Main(string[] args)
{
// create our NHibernate session factory
var sessionFactory = CreateSessionFactory();
using (var session = sessionFactory.OpenSession())
{
// populate the database
using (var transaction = session.BeginTransaction())
{
// create a couple of Stores each with some Products and Employees
var topShelf = new Shelf();
var sw = new Stopwatch();
sw.Start();
for (var i = 0; i < 1000; i++)
{
var potatoes = new Product { Name = "Potatoes" + i.ToString(), Price = 3.60 + i };
var meat = new Product { Name = "Meat" + i.ToString(), Price = 4.49 + i };
//session.SaveOrUpdate(potatoes); //===<<cascading save handles this :-)
//session.SaveOrUpdate(meat);
topShelf.Products.Add(meat);
topShelf.Products.Add(potatoes);
}
sw.Stop();
session.SaveOrUpdate(topShelf);
//session.SaveOrUpdate(superMart);
transaction.Commit();
Console.WriteLine("Add Items: " + sw.ElapsedMilliseconds);
}
}
using (var session = sessionFactory.OpenSession())
{
// retreive all stores and display them
using (session.BeginTransaction())
{
var shelves = session.CreateCriteria(typeof(Shelf)).List<Shelf>();
foreach (var store in shelves)
{
WriteShelfPretty(store);
}
}
}
Console.ReadLine();
}
private const string DbFile = "FIVEProgram.db";
private static ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(SQLiteConfiguration.Standard.UsingFile(DbFile))
.Mappings(m => m.AutoMappings
.Add(AutoMap.AssemblyOf<Shelf>(type => type.Namespace.EndsWith("Entities"))
.Override<Shelf>(map =>
{
map.HasManyToMany(x => x.Products);//.Cascade.All();
})
.Conventions.AddFromAssemblyOf<CascadeAll>()
)
) //emd mappings
.ExposeConfiguration(BuildSchema)//Delete and remake db (see function below)
.BuildSessionFactory();//finalizes the whole thing to send back.
}
private static void BuildSchema(Configuration config)
{
// delete the existing db on each run
if (File.Exists(DbFile))
File.Delete(DbFile);
// this NHibernate tool takes a configuration (with mapping info in)
// and exports a database schema from it
new SchemaExport(config)
.Create(false, true);
}
private static void WriteShelfPretty(Shelf shelf)
{
Console.WriteLine(shelf.Id);
Console.WriteLine(" Products:");
foreach (var product in shelf.Products)
{
Console.WriteLine(" " + product.Name);
}
Console.WriteLine();
}
}
}
//Data Classes
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace App4.Entities
{
public class Product
{
public virtual int Id { get; private set; }
public virtual string Name { get; set; }
public virtual double Price { get; set; }
}
public class Shelf
{
public virtual int Id { get; private set; }
public virtual IList<Product> Products { get; private set; }
public Shelf()
{
Products = new List<Product>();
}
}
}
//Cascade All Helper Class
using FluentNHibernate.Conventions;
using FluentNHibernate.Conventions.AcceptanceCriteria;
using FluentNHibernate.Conventions.Inspections;
using FluentNHibernate.Conventions.Instances;
using System;
using System.Collections.Generic;
namespace App4
{
public class CascadeAll :
IHasOneConvention, //Actually Apply the convention
IHasManyConvention,
IReferenceConvention,
IHasManyToManyConvention,
IHasOneConventionAcceptance, //Test to see if we should use the convention
IHasManyConventionAcceptance, //I think we could skip these since it will always be true
IReferenceConventionAcceptance, //adding them for reference later
IHasManyToManyConventionAcceptance
{
//One to One
public void Accept(IAcceptanceCriteria<IOneToOneInspector> criteria)
{
//criteria.Expect(x => (true));
}
public void Apply(IOneToOneInstance instance)
{
instance.Cascade.All();
}
//One to Many
public void Accept(IAcceptanceCriteria<IOneToManyCollectionInspector> criteria)
{
//criteria.Expect(x => (true));
}
public void Apply(IOneToManyCollectionInstance instance)
{
instance.Cascade.All();
}
//Many to One
public void Accept(IAcceptanceCriteria<IManyToOneInspector> criteria)
{
// criteria.Expect(x => (true));
}
public void Apply(IManyToOneInstance instance)
{
instance.Cascade.All();
}
//Many to Many
public void Accept(IAcceptanceCriteria<IManyToManyCollectionInspector> criteria)
{
// criteria.Expect(x => (true));
}
public void Apply(IManyToManyCollectionInstance instance)
{
instance.Cascade.All();
}
}
}
The signature for the conventions has changed. Are you not using something like ReSharper? That would point you to that conclusion.
You can read more about the new conventions on the wiki.

Categories