How to implement table-per-concrete-type strategy using entity framework
I'm mapping a set of tables that share a common set of fields:
So as you can see I'm using a table-per-concrete-type strategy to map the inheritance.
But...
I have not could to relate them to an abstract type containing these common properties.
It's possible to do it using EF?
BONUS: The only non documented Entity D开发者_开发知识库ata Model Mapping Scenario
is Table-per-concrete-type inheritance
http://msdn.microsoft.com/en-us/library/cc716779.aspx : P
Finally I created an interface 'Iggy' that contained the accessors to the common properties:
public Interface Iggy
{
string modifiedBy { get; set; }
DateTime modifiedDate { get; set; }
}
and used partial classes to implement it in the domain classes
public partial class Al:Iggy{}
public partial class Ben:Iggy{}
public partial class Carl:Iggy{}
C# is really very handy, and although I would liked to do it using a entity-framework feature, partials work like a charm : )
Why not fix the table design?!
I have achieved this exact scenario today. The designer does not seem to do it properly, but here is how I did it by modifying the EDMX: -
- In the base class, put all your shared properties e.g modified date and modified by. You can do this in the CSDL section. Do not put any conditions on the CSDL.
- In the C-S mapping content, put the mappings of your fields in. Obviously you need to map on both child entities the shared properties to the physical DB columns.
- Put a condition on each table where the PK column e.g. Id sets IsNull=false.
If you then reopen the designer, you should see that the shared fields are in the base class, and the only fields that appear on the derived types (in the Column Mappings area) will be the unique columns.
At least, this worked for me :-)
Actually, using partial classes to implement the interface really does solve your problem if you have just a few tables (entities) that you want to map.But when you need to do this with more entities or with even more interfaces, you could use the T4 template used by EF to generate the classes, and then implement the interface directly at the POCO auto-generated classes,with no need of manual work.
I have done this myself, we have created a ISimpleAuditable interface, very similar to yours, and the T4 checks if the table has the correct fields, and if it does, it will add the interface implementation code to the class.
We have created a Include.tt file that has code like this:
public static string GetTypeInterfaces(EntityType entity) { string interfaces = String.Empty;
if(IsNome(entity))
{
if(IsIdentifiableNumeric(entity))
interfaces = "IEntity<int>";
if(IsIdentifiableText(entity))
interfaces = "IEntity<string>";
if (interfaces == String.Empty)
interfaces = "INome";
if(IsSimpleAuditable(entity))
if (interfaces==String.Empty)
interfaces = "ISimpleAuditable<string>";
else
interfaces += ", ISimpleAuditable<string>";
}
else
{
if(IsIdentifiableNumeric(entity))
interfaces = "IIdentifiable<int>";
if(IsIdentifiableText(entity))
interfaces = "IIdentifiable<string>";
if(IsSimpleAuditable(entity))
if (interfaces==String.Empty)
interfaces = "ISimpleAuditable<string>";
else
interfaces += ", ISimpleAuditable<string>";
}
if (interfaces != string.Empty)
if (entity.BaseType !=null)
interfaces = string.Format(", {0}", interfaces);
else
interfaces = string.Format(": {0}", interfaces);
return interfaces;
}
The T4 code looks something like this:
<#@ template language="C#" debug="true" hostspecific="true"#>
<#@ import namespace="System.Diagnostics" #>
<#@ include file="EF.Utility.CS.ttinclude"#>
<#@ include file="Winsys.Sandstone.Data.ttinclude"#><#@
output extension=".cs"#><#
const string inputFile = @"Winsys.Sandstone.Data.edmx";
var textTransform = DynamicTextTransformation.Create(this);
var code = new CodeGenerationTools(this);
var ef = new MetadataTools(this);
var typeMapper = new TypeMapper(code, ef, textTransform.Errors);
var fileManager = EntityFrameworkTemplateFileManager.Create(this);
var itemCollection = new EdmMetadataLoader(textTransform.Host, TextTransform.Errors).CreateEdmItemCollection(inputFile);
var codeStringGenerator = new CodeStringGenerator(code, typeMapper, ef);
if (!typeMapper.VerifyCaseInsensitiveTypeUniqueness(typeMapper.GetAllGlobalItems(itemCollection), inputFile))
{
return string.Empty;
}
WriteHeader(codeStringGenerator, fileManager);
foreach (var entity in typeMapper.GetItemsToGenerate<EntityType>(itemCollection))
{
fileManager.StartNewFile(entity.Name + ".cs");
BeginNamespace(code);
#>
<#=codeStringGenerator.UsingDirectives(inHeader: false)#>
<#=codeStringGenerator.EntityClassOpening(entity)#>
{
<#
var propertiesWithDefaultValues = typeMapper.GetPropertiesWithDefaultValues(entity);
var collectionNavigationProperties = typeMapper.GetCollectionNavigationProperties(entity);
var complexProperties = typeMapper.GetComplexProperties(entity);
if (propertiesWithDefaultValues.Any() || collectionNavigationProperties.Any() || complexProperties.Any())
{
#>
public <#=code.Escape(entity)#>()
{
<#
foreach (var edmProperty in propertiesWithDefaultValues)
{
#>
this.<#=code.Escape(edmProperty)#> = <#=typeMapper.CreateLiteral(edmProperty.DefaultValue)#>;
<#
}
foreach (var navigationProperty in collectionNavigationProperties)
{
#>
this.<#=code.Escape(navigationProperty)#> = new List<<#=typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType())#>>();
<#
}
foreach (var complexProperty in complexProperties)
{
#>
this.<#=code.Escape(complexProperty)#> = new <#=typeMapper.GetTypeName(complexProperty.TypeUsage)#>();
<#
}
#>
}
<#
}
var simpleProperties = typeMapper.GetSimpleProperties(entity);
if (simpleProperties.Any())
{
foreach (var edmProperty in simpleProperties)
{
#>
<#=codeStringGenerator.Property(edmProperty)#>
<#
}
}
if (complexProperties.Any())
{
#>
<#
foreach(var complexProperty in complexProperties)
{
#>
<#=codeStringGenerator.Property(complexProperty)#>
<#
}
}
var navigationProperties = typeMapper.GetNavigationProperties(entity);
if (navigationProperties.Any())
{
#>
<#=WinsysGenerator.GetSimpleAuditable(entity)#>
<#
foreach (var navigationProperty in navigationProperties)
{
#>
<#=codeStringGenerator.NavigationProperty(navigationProperty)#>
<#
}
}
#>
Note that this is not the full T4, but you can get the idea
I like answer by @SDReyes above. But using interface and partial classes is not always convenient. When you use interface you should obligatory repeat same set of default interface accessors in each inherited partial class. In case of a single inheritance, using an abstract class is much more convenient. By default, abstract class will give exactly desired mapping:
Table per Concrete class (TPC): This approach suggests one table for one concrete class, but not for the abstract class. So, if you inherit the abstract class in multiple concrete classes, then the properties of the abstract class will be part of each table of the concrete class.
http://www.entityframeworktutorial.net/code-first/inheritance-strategy-in-code-first.aspx
Then we can rewrite code by @SDReyes as follows:
public abstract class Iggy
{
string modifiedBy { get; set; }
DateTime modifiedDate { get; set; }
}
public class Al:Iggy{}
public class Ben:Iggy{}
public class Carl:Iggy{}
We can leave Al, Ben and Carl definitions empty. All inherited fields from Iggy will be automatically taken from definition of Iggy into an individual table per class.
精彩评论