I am using dapper to map SQL result set directly to my C# object, everything works nicely.
I am using statements like this to do the mapping
var result = connection.Query< MyClass >( "sp_select", );
but this statement doesn't seem to enforce exact mapping between the class fields and the columns returned from the database. Meaning, it won't fail when the field on the POCO doesn't exist on the result set.
I do enjoy the fact that the implementation is loose and doesn't enforce any restriction right of the bat, but is there any feature of dapper that would allow me to demand certain fields from the result set before deeming the mapping successful?
You can also try Dapper-Extensions
Here is an example:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
}
[TestFixture]
public class DapperExtensions
{
private SqlConnection _connection;
[SetUp]
public void Init()
{
_connection = new SqlConnection(#"Data Source=.\sqlexpress;Integrated Security=true; Initial Catalog=mydb");
_connection.Open();
_connection.Execute("create table Person(Id int not null, FirstName varchar(100) not null, LastName varchar(100) not null)");
_connection.Execute("insert into Person(Id, FirstName, LastName) values (1, 'Bill', 'Gates')");
}
[TearDown]
public void Teardown()
{
_connection.Execute("drop table Person");
_connection.Close();
}
[Test]
public void Test()
{
var result = _connection.Get<Person>(1);
}
}
The test will fail due to a missing Address column in the Person table.
You can also ignore columns with Custom Maps:
public class PersonMapper : ClassMapper<Person>
{
public PersonMapper()
{
Map(x => x.Address).Ignore();
AutoMap();
}
}
There is no way for you to enforce this "automagically" with an attribute or a flag. You can follow this open Github issue for more background.
This could be accomplished by you manually by mapping each property yourself in a select clause, although at that point you've lost a lot of the power and ease of use of Dapper.
var result = connection.Query<MyClass>("sp_select")
.Select(x =>
{
// manually map each property and verify
// that the data is returned
});
Related
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 3 days ago.
This post was edited and submitted for review 3 days ago.
Improve this question
How can you call a stored procedure using Entity Framework Core and return the results using a generic class?
In .NET Standard Entity Framework, I'm able to call stored procedures and return generic like so:
public IEnumerable<T> ExecuteStoredProcedure<T>(object[] sqlParameters) where T : IStoredProcedure, new()
{
if (sqlParameters == null) sqlParameters = new object[] { };
return DataContext.Database.SqlQuery<T>((new T()).Query, sqlParameters).ToList();
}
This method is not available in the same fashion in Entity Framework Core anymore...
Assuming you're using Code First and are comfortable with the steps necessary to create the actual stored procedure and have that defined in your database, I will focus my answer on how to call that stored procedure in C# and map the results generically.
First you want to create a model that matches the data you expect to get back from your results.
Here is an example:
public class UserTimesheet : IStoredProcedure
{
public string Query => "[dbo].[GetUserTimesheet] #userId, #month, #year";
public DateTime WorkDate { get; set; }
public Guid ProjectId { get; set; }
public int CategoryId { get; set; }
public string? ProjectName { get; set; }
public decimal? Hours { get; set; }
}
Notice this extends an interface called IStoredProcedure with the Query property. More on that later, but it's there to work by convention.
Here is that interface:
public interface IStoredProcedure
{
string Query { get; }
}
Next you'll want to add a DbSet to your database context.
// Put this in your Database Context
public DbSet<UserTimesheet> UserTimesheets { get; set; } = null!;
Now since this doesn't map to an actual table, you will want to add some code to the OnModelCreating to tell EF how to reference it. Again, I'm working on a convention, in this case I only want to apply this setting to models that implement IStoredProcedure, and we can do that with a little reflection to make life easier.
In this case we're going to say it has no key and treat it like a view. I created an extension method to keep things a little cleaner, you can use it like this:
public static class ModelBuilderExtension
{
public static ModelBuilder ConfigureStoredProcedureDbSets(this ModelBuilder modelBuilder)
{
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
if (typeof(IStoredProcedure).IsAssignableFrom(entityType.ClrType))
{
modelBuilder.Entity(entityType.ClrType).HasNoKey().ToView(null);
}
}
return modelBuilder;
}
}
// Put this in DatabaseContext class
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ConfigureStoredProcedureDbSets();
}
Next you'll want something to hold your generic code. I use a class called StoredProcedureRepository, but you can call it what you like. Here's the code, including the interface (for Dependency Injection, if you like):
public abstract class Repository
{
protected readonly DatabaseContext _context;
protected Repository(DatabaseContext context)
{
_context = context;
}
}
public interface IStoredProcedureRepository
{
IEnumerable<T> ExecuteStoredProcedure<T>(object[] sqlParameters) where T : class, IStoredProcedure, new();
SqlParameter GetSqlParameter(string name, object value, bool isOutput = false);
}
public class StoredProcedureRepository : Repository, IStoredProcedureRepository
{
#region Properties
private const string SQL_PARAMETER_PREFIX = "#";
#endregion
#region Constructor
public StoredProcedureRepository(DatabaseContext context) : base(context)
{
}
#endregion
#region Shared Public Methods
public IEnumerable<T> ExecuteStoredProcedure<T>(object[] sqlParameters) where T : class, IStoredProcedure, new()
{
return _context.Set<T>().FromSqlRaw<T>((new T()).Query, sqlParameters).ToList();
}
public SqlParameter GetSqlParameter(string name, object value, bool isOutput = false)
{
if (!name.StartsWith(SQL_PARAMETER_PREFIX))
{
name += SQL_PARAMETER_PREFIX;
}
var direction = isOutput ? System.Data.ParameterDirection.Output : System.Data.ParameterDirection.Input;
return new SqlParameter
{
ParameterName = name,
Value = value,
Direction = direction
};
}
#endregion
}
There are a few things to notice here. It's referencing the DbSet generically and using FromRawSql and calling the Query (string) property from the IStoredProcedure implementation of your model. So you'll want to make sure that contains your query to execute the stored procedure, in this example that would be "[dbo].[GetUserTimesheet] #userId, #month, #year"
Now you can call this stored procedure generically.
Here is an example:
var parameters = new object[3];
parameters[0] = GetSqlParameter("#userId", userId);
parameters[1] = GetSqlParameter("#month", month);
parameters[2] = GetSqlParameter("#year", year);
IList<UserTimesheet> queryResults = _storedProcedureRepository.ExecuteStoredProcedure<UserTimesheet>(parameters).ToList();
To add new stored procedures, just create their respective models (being sure to implement IStoredProcedure and define their Query property, then add their DbSet to the database context.
For example:
public class UserProject : IStoredProcedure
{
public string Query => "[dbo].[GetUserProjects] #userId";
public Guid ProjectId { get; set; }
public string ProjectName { get; set; }
}
// add this to the database context
public DbSet<UserProject> UserProjects { get; set; } = null!;
then call it like so:
var parameters = new object[1];
parameters[0] = GetSqlParameter("#userId", userId);
IList<UserProject> queryResults = _storedProcedureRepository.ExecuteStoredProcedure<UserProject>(parameters).ToList();
I have a model with a linked list of foreign keys i.e.
[Table("a"]
public class A {
[Key]
[Column("a_id")]
public int Id { get; set; }
public List<B> Bs { get; set; } = new List<B>();
}
[Table("b"]
public class B {
[Key]
[Column("b_id")]
public int Id { get; set; }
[NotMapped]
public string MyFunctionValue { get; set; }
[ForeignKey("a_id")]
public A A { get; set; }
}
I've then defined a function which links to a scalar sql function like so...
public static class MySqlFunctions {
[DbFunction("MyFunction", "dbo")]
public static string MyFunction(int bId) {
throw new NotImplementedException();
}
}
and registered in my context like so...
modelBuilder.HasDbFunction(() => MySqlFunctions.MyFunction(default));
What I want to be able to do in my repository class is to grab the A records with the linked B records in a List with their MyFunctionValue value set to the return value of the function when ran against the id of B. Something like...
myContext.A
.Include(a => a.Bs.Select(b => new B {
Id = b.Id,
MyFunctionValue = MySqlFunctions.MyFunction(b.Id)
});
However with all the options I've tried so far I'm getting either a InvalidOperationException or NotImplementedException I guess because it can't properly convert it to SQL?
Is there any way I can write a query like this or is it too complex for EF to generate SQL for? I know there's a possibility I could use .FromSql but I'd rather avoid it if possible as it's a bit messy.
EDIT:
So I've managed to get it working with the following code but it's obviously a bit messy, if anyone has a better solution I'd be grateful.
myContext.A
.Include(a => a.Bs)
.Select(a => new {
A = a,
MyFunctionValues = a.Bs.Select(b => MySqlFunctions.MyFunction(b.Id))
})
.AsEnumerable()
.Select(aWithMfvs => {
for (int i = 0; i < aWithMfvs.MyFunctionValues.Count(); i++) {
aWithMfvs.A.Bs[i].MyFunctionValue = aWithMfvs.MyFunctionValues[i];
}
return aWithMfvs.A;
})
.AsQueryable();
There are several things you should consider with db functions:
When you declare a DbFunction as static method, you don't have to register it with the modelBuilder
Registering is only needed, when you would use Fluent API (which IMHO I recommend anyway in order to have you entities free of any dependencies)
The return value, the method name and the count, type and order of the method parameters must match your code in the user defined function (UDF)
You named the method parameter as bId. Is it exactly the same in your UDF or rather as in the table like b_id?
This resource explains how Computed excludes a property (in an update only?).
Specifie the property should be excluded from update.
[Table("Invoice")]
public class InvoiceContrib
{
[Key]
public int InvoiceID { get; set; }
public string Code { get; set; }
public InvoiceKind Kind { get; set; }
[Write(false)]
[Computed]
public string FakeProperty { get; set; }
}
using (var connection = My.ConnectionFactory())
{
connection.Open();
var invoices = connection.GetAll<InvoiceContrib>().ToList();
// The FakeProperty is skipped
invoices.ForEach(x => x.FakeProperty += "z");
var isSuccess = connection.Update(invoices);
}
Doesn't Write(false) fulfill the same purpose though? What's the difference between [Computed] and [Write(false)]?
Edit:
I've just checked the resource linked in response to my question. It almost hits the nail on this! Could someone please confirm if both attributes perform the same operations, but are just worded in two different ways, as to give a better abstraction to their users?
Both [Computed] and Write(false) will ignore the property while INSERT as well as UPDATE operations. So, both of them are same. You can use any one of it.
Documentation says below:
[Write(true/false)] - this property is (not) writeable
[Computed] - this property is computed and should not be part of updates
About Write:
As stated in first line in document above, Write handles "writeable" behavior. This should include both INSERT and UPDATE.
This can also be confirmed in source code here:
var properties = type.GetProperties().Where(IsWriteable).ToArray();
...
...
...
private static bool IsWriteable(PropertyInfo pi)
{
var attributes = pi.GetCustomAttributes(typeof(WriteAttribute), false).AsList();
if (attributes.Count != 1) return true;
var writeAttribute = (WriteAttribute)attributes[0];
return writeAttribute.Write;
}
About Computed:
Second line in document above is bit broad though.
should not be part of updates
Does that mean it can be the part of INSERT? No, it does not; it also cover both the actions. This can be observed with below code:
CREATE TABLE TestTable
(
[ID] [INT] IDENTITY (1,1) NOT NULL CONSTRAINT TestTable_P_KEY PRIMARY KEY,
[Name] [VARCHAR] (100) NOT NULL,
[ComputedCol] [VARCHAR] (100) NOT NULL DEFAULT '',
[NonWriteCol] [VARCHAR] (100) NOT NULL DEFAULT ''
)
[Table("TestTable")]
public class MyTable
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
[Computed]
public string ComputedCol { get; set; }
[Write(false)]
public string NonWriteCol { get; set; }
}
int id;
using(SqlConnection conn = new SqlConnection(#"connection string"))
{
MyTable myTable = new MyTable();
myTable.Name = "Name";
myTable.ComputedCol = "computed";
myTable.NonWriteCol = "writable";
conn.Insert<MyTable>(myTable);
id = myTable.ID;
}
using(SqlConnection conn = new SqlConnection(#"connection string"))
{
MyTable myTable = conn.Get<MyTable>(id);
myTable.Name = "Name_1";
myTable.ComputedCol = "computed_1";
myTable.NonWriteCol = "writable_1";
conn.Update<MyTable>(myTable);
}
With above code, you will observe that no matter which attribute you choose to decorate the property, it will neither be considered for INSERT nor for UPDATE. So basically, both the attributes are playing same role.
This can be further confirmed in Dapper.Tests.Contrib test project on github.
[Table("Automobiles")]
public class Car
{
public int Id { get; set; }
public string Name { get; set; }
[Computed]
public string Computed { get; set; }
}
...
...
...
//insert with computed attribute that should be ignored
connection.Insert(new Car { Name = "Volvo", Computed = "this property should be ignored" });
Source: 1 and 2
Looking at the comment and the value assigned to the property in above code, it makes clear that Computed should also ignore the property for INSERT operation; it is expected result of the test.
Why those two ways are provided for same purpose is not known. It causes confusion.
Following are some additional references:
Comment 1
I use [Computed] or [Write("False")] for that. Does that not work for your scenario?
Comment 2
Glad I could help. Every day is a school day! I'm not sure why they both exist though as I think they are functionally the same. I tend to use [Computed] just because it is marginally easier to type.
Comment 3
I understand that using Dapper.Contrib I can use the Write and Computed attributes to ignore properties during write operations. However, this will ignore the properties on both insert and update. I need a way to ignore properties on updates. My suggestion would be to add 2 attributes... perhaps named Insertable(bool) and Updateable(bool). When a false value is passed to these the framework would exclude that property for the given operation. This is a lightweight, straightforward approach to a very common problem.
I don't think Computed attribute has anything to do with Computed Columns as Dapper.Contrib support multiple RDBMS.
I'm looking for advice on the most efficient way to process a variable size dataset.i have a user requirement to provide a web interface to enable the user to upload an excel sheet containing a list of record ids, the fields to update and the new value, each row can be a different field and a different value and the number of rows can vary from a few dozen up to around 20,000. The destination table is in a Microsoft SQL database
The technology stack I'm using is C#, MVC using WCF to a custom ESB, MSMQ, Entity Framework(but i can't change table structure to enable optimistic concurrency) and MS SQL.
So parsing the datasource is fine but i'm unsure as to best way to proceed from there.
Am i better of creating an individual message for each row or do should i parse the result set and group messages where possible(i.e where the field name and value match) into a single larger update statement and pass that as the message
And am i better to update via Entity Framework directly or use a stored procedure?
Here's a little helper method to update an EF entity based on a list of name/value pairs;
public void Update<T>(T entity, Dictionary<string, string> valuesToUpdate) where T : class
{
var entry = ChangeTracker.Entries<T>().Where(e => object.ReferenceEquals(e.Entity, entity)).Single();
foreach (var name in valuesToUpdate.Keys)
{
var pi = typeof(T).GetProperty(name);
pi.SetValue(entity, Convert.ChangeType(valuesToUpdate[pi.Name], pi.PropertyType));
entry.Property(pi.Name).IsModified = true;
}
}
And a full example of how you would use it:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
namespace Ef6Test
{
public class Car
{
public int Id { get; set; }
public string Name { get; set; }
public int Color { get; set; }
public DateTime UpdateDate { get; set; }
}
class Db : DbContext
{
public void Update<T>(T entity, Dictionary<string, string> valuesToUpdate) where T : class
{
var entry = ChangeTracker.Entries<T>().Where(e => object.ReferenceEquals(e.Entity, entity)).Single();
foreach (var name in valuesToUpdate.Keys)
{
var pi = typeof(T).GetProperty(name);
pi.SetValue(entity, Convert.ChangeType(valuesToUpdate[pi.Name], pi.PropertyType));
entry.Property(pi.Name).IsModified = true;
}
}
public DbSet<Car> Cars { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new DropCreateDatabaseAlways<Db>());
using (var db = new Db())
{
db.Database.Log = m => Console.WriteLine(m);
db.Database.Initialize(true);
}
int id;
using (var db = new Db())
{
db.Database.Log = m => Console.WriteLine(m);
var c = db.Cars.Create();
c.Color = 2;
c.UpdateDate = DateTime.Now;
db.Cars.Add(c);
db.SaveChanges();
id = c.Id;
}
using (var db = new Db())
{
db.Database.Log = m => Console.WriteLine(m);
var c = new Car() { Id = id };
var updates = new Dictionary<string, string>();
updates.Add(nameof(Car.Color), "3");
updates.Add(nameof(Car.UpdateDate), "2017-01-02");
db.Cars.Attach(c);
db.Update(c, updates);
db.SaveChanges();
}
Console.WriteLine("Hit any key to exit");
Console.ReadKey();
}
}
}
}
And here's the UPDATE EF Generates:
UPDATE [dbo].[Cars]
SET [Color] = #0, [UpdateDate] = #1
WHERE ([Id] = #2)
-- #0: '3' (Type = Int32)
-- #1: '1/2/2017 12:00:00 AM' (Type = DateTime2)
-- #2: '1' (Type = Int32)
Note that only the changed attributes are modified, Name is not.
I always want to go for type safety. Therefore I would create a class to display your values and use a generic adapter class that handles the fetching and the updating of the values of your database.
Your display class will need something like this:
abstract class DisplayedValue
{
public int Id {get; protected set;}
public string FieldDescription {get; protected set;}
public abstract string Value {get; set;}
}
We want the compiler to complain if you try to assign an integer value to a DateTime, or other invalid conversions. So we need a generic class that will hold the fetched value, and translate the displayed value into a fetched value
class Display<Tproperty> : Display
{
public override string Value
{
get {return this.FetchValue.ToString();}
set {this.SetValue(Parse(value));}
}
public Func<string, TProperty> Parse {get; set;}
public Func<int, TProperty> FetchValue {get; set;}
public Action <int, TProperty> SetValue {get; set;}
}
This class represents the original value of the property you want to display. Because I don't know the type of items you want to display in your rows (simple numbers? Guids? Customer names?), I need a Parse function that parses the string to update into a value to update.
TODO: if ToString() is not suitable to convert your property into the displayed value, consider using a Func property that converts your TProperty into a DisplayValue:
public Func<TProperty, string> ToDisplayValue {get; set;}
TODO: to increase performance, consider keeping track on whether the data has already been fetched and translated, and not fetch / translate it again if asked for.
FetchValue is a Function that takes an int Id, and returns the Tproperty value of the item that must be displayed.
UpdateValue is a void Function that takes as input an Id, and a Tproperty value to update. It updates the proper value
So to create a Display object you need:
Id to display
FieldDescription
Parse function that parses the displayed value into a TProperty value
A function to fetch the data
A void function to update the data
Did you notice, that in this class I never mentioned that I use a database to fetch or to update the data. This is hidden in the delegate functions to Fetch and Update the data. This allows reuse to store data in other media, like variables, streams, files, etc
As an example: a SchoolDbContext with a Student:
class Student
{
public int Id {get; set;} // primary Key
public DateTime Birthday {get; set;
public string FirstName {get; set;}
... // other properties
}
class SchoolDbContext : DbContext
{
public DbSet<Student> Students {get; set;} // the table you want to update
... // other tables
}
Suppose you want Display a row that can update the Birthday of the Student with Id myStudentId.
int myStudentId = ...
MyDbContext myDbContext = ...
DisplayedValue birthday = new Display<DateTime>()
{
Id = myStudentId,
FieldDescription = "Birthday",
// Parse function to parse the update string to a DateTime
Parse = (txt) => DateTime.Parse(txt),
// function to parse the DateTime to a displayable string
ToDisplayValue = (birthday) => birthDay.ToString("yyyy/MMM/DD"),
// the function that fetches the Birthday of Student with Id from myDbContext:
FetchValue = (id) => myDbContext.Students
.Where(student => student.Id == id)
.Select(student => student.Birthday)
.SingleOrDefault();
// the function that updates the Birthday of the Student with Id from myDbContext:
UpdateValue = (id, valueToUpdate) =>
{
Student studentToUpdate = dbContext.Students
.Where(student => student.Id == id)
.SingleOrDefault();
studentToUpdate.BirthDay = valueToUpdate);
myDbContext.SaveChanges();
},
}
Although this is a very neat and reusable solution it is quite a lot of work for every item you want to display. If you want to automate this in a factory, you run into several problems
You need to be sure every item needs to have an Id
How to get the descriptive name of the displayed item? Is property name enough?
.
interface IId
{
int Id {get;}
}
You need to be sure that every class in your DbContext that will be a DbSet derives from this interface.
public DisplayFactory
{
public MyDbContext MyDbContext {get; set;}
public Display<TProperty> Create<TEntity, TProperty>(int id,
Expression<Func<TEntity, TProperty>> propertySelector,
Action<TEntity, TProperty> propertyUpdater,
Func<string, TProperty> parse,
Func<TProperty, string> toDisplayValue)
{
return new Display<TProperty>()
{
Id = id,
Parse = parse,
ToDisplayValue = toDisplayValue,
FetchValue = (id) => this.MyDbContext.DbSet<TEntity>()
.Where(entity => entity.Id == id) // this is where I need the interface
.Select(propertySelector)
.SingleOrDefault(),
SetValue = (id, valueToUpdate) =>
{
TEntity entityToUpdate = this.MyDbContext.DbSet<TEntity>()
.Where(entity => entity.Id == id)
.SingleOrDefault();
propertyUpdate(entityToUpdate, valueToUpdate);
SaveChanges();
}
}
}
Usage:
DisplayFactory factory = new DisplayFactory()
{
MyDbContext = ...
}
DisplayedValue createdValue = factory.Create(id,
student => student.Birthday, // property selector
(student, value) => student.Birthday = value; // property updater
(txt) => DateTime.Parse(txt), // string to Datetime
(birthday) => birthDay.ToString(...)); // to displayed birthday
Note, this is completely type safe, The compiler won't accept it if you want to update non-existing columns or non-existing types or want to assign incompatible types, like assigning an int to a DateTime. You can't by accident update another property than you just displayed.
If you still think that this is too much work, consider using reflection and PropertyInfo to select the DbSetand the Column you want to update.
Keep in mind however, that you still have to provide parsers to display and parse the displayed string values into values to update. You'll loose all type safety and the compiler will accept it if you use names of non-existing tables or columns.
I'm not sure if the extra testing time weighs up to the saved typing time.
I'm using Fluent-NHibernate and attempting to persist an object hierarchy using the table per subclass method:
public class AbstractProduct
{
public int ProductId { get; set; }
public string Name { get; set; }
}
public class SingleProduct : AbstractProduct
{
public int SingleProductId { get; set; }
public string SomeField { get; set; }
}
when saving an object
var singleProduct = new SingleProduct();
session.SaveOrUpdate(singleProduct);
I get this error:
NHibernate.Exceptions.GenericADOException: could not insert: [FluentNHibernateSubClassTest.SingleProduct#3][SQL: INSERT INTO SingleProductData (Field1, AbstractProduct_id) VALUES (?, ?)] ---> System.Data.SqlClient.SqlException: Invalid column name 'AbstractProduct_id'.
despite having the following overrides:
public class AbstractProductOverrides : IAutoMappingOverride<AbstractProduct>
{
public void Override(AutoMapping<AbstractProduct> mapping)
{
mapping.Id(x => x.ProductId).Column("ProductId");
//this mapping provided to illustrate the overrides are picked up
mapping.Table("ProductsData");
mapping.JoinedSubClass<SingleProduct>("ProductId");//ignored??
}
}
public class SingleProductOverrides : IAutoMappingOverride<SingleProduct>
{
public void Override(AutoMapping<SingleProduct> mapping)
{
mapping.Id(x => x.SingleProductId);
mapping.Table("SingleProductData");
mapping.Map(x => x.SomeField).Column("Field1");
}
}
It doesn't appear to matter what column name I supply to JoinedSubClass it ignores it and uses AbstractProduct_id instead.
How can I tell nhibernate the key column is ProductId and not AbstractProduct_id?
I have a test project demonstrating the issue available here (you need to create the db)
UPDATE
I've got around this by providing the following convention:
public class JoinedSubclassConvention : IJoinedSubclassConvention
{
public void Apply(IJoinedSubclassInstance instance)
{
if (instance.EntityType == typeof(SingleProduct))
instance.Key.Column(("ProductId"));
}
}
which works but feels like its the wrong way or a hack.
mapping.Id in SingleProductOverrides is flawed. Subclasses don't have their own id, they inherit the Id from their base classes. Even mapping.JoinedSubClass<SingleProduct>("ProductId"); is redundant (probably ignored) if SingleProduct is automapped as well (it is as seen from the Override for it). JoinedSubclassConvention is the right way to do this.