I am working on a brand new app and decided to try DDD for the first time, so bear with me on that front but please correct me where I am wrong on my understanding of how to implement. I have watched a great deal of Pluralsight videos on the subject now, but they don't deal with these more advanced use cases so not sure where to go. Have tried several things and I am stuck on what to do. One of those first things you hit after the tutorial deals. :)
I have a fairly complicated domain but I will only show the simple example part that matters for this question. I have 2 schemas...identity and network. In those schemas, I have 2 tables. In Identity, I have a normal aspnet_users table (user from now on) for users in the system. In the Network schema, I have an aggregate root called Network. The network is the heart of a large part of my domain and is just a collection of items for sake of a long explanation. In a network, I can have 0, 1, or many items...such as an 'event'. When a user creates an event within the network, I need to be able to capture the user that created the event. Not a hard biz requirement, or a db requirement...stick a created_by_user_id field on the Network table and call it good.
This is where my issues start though. While this isn't a big deal when the user is in the same bounded context, my user table is in the IdentityDbContext and the Event table is in the NetworkDbContext.
I have attached relevant classes for points below...if you think something is missing that you need, please ask and I will provide. I didn't add the stuff around users since it is truly just the microsoft aspnet users table implementation.
Here you can see, the network domain entity contains "Name" which is a Value Object that wraps up the Name of the network. This all works fine. The CreateByUserId without the navigation property doing the non-DDD way works fine. When I started making it DDD friendly, I wanted to remove the CreatedByUserId field and only have the CreatedByUser. The issue is that the User that this points to is in the Identity context, not the network context.
DOMAIN CLASS - NETWORK
public class Network : IntegerEntity
{
public NetworkName Name { get; set; }
public int CreatedByUserId { get; private set; }
public User CreatedByUser { get; private set; }
private Network() { }
public Network(NetworkName name, User createdByUser)
{
Name = name;
CreatedByUser = createdByUser;
}
public Network(int id, NetworkName name, User createdByUser) : base(id)
{
Name = name;
CreatedByUser = createdByUser;
}
}
Here in the type configuration, you can see I have a couple things that need to be pointed out:
I am using EF Type Configuration with migrations. I have attached the type config for my network root/table (NetworkTypeConfig). The NetworkBaseTypeConfiguration base class just points that table to the "network" schema so each table's code didn't have to specify it. But that Base class also derives from a class called BaseTypeConfiguration. That class orchestrates the creation of the table for the type config....basically allows you to override which functions you need...you see that network is configuring columns, relationships, and ignored relationships currently, but the 2 latter ones aren't actually doing anything right now.
We are using Postgres....which is not case friendly so we elected to lowercase all table and fields so we don't have to write queries with quotes. This is the .HasColumnName stuff...all works fine, but I do need it to work on all tables/columns/nav props.
TYPE CONFIGURATION - NETWORK
public class NetworkTypeConfiguration : NetworkBaseTypeConfiguration<Network, int>
{
public NetworkTypeConfiguration() : base(TableEnums.CoreTables.Networks.GetName()) { }
public override void ConfigureColumns()
{
base.ConfigureColumns();
Builder.Property(x => x.Name)
.IsRequired()
.HasColumnName(NetworksEnums.NetworksColumns.Name.GetName())
.HasConversion(x => x.Name, y => NetworkName.Create(y).Value);
Builder.Property(x => x.CreatedByUserId)
.IsRequired()
.HasColumnName(NetworksEnums.NetworksColumns.Created_By_User_Id.GetName());
}
public override void ConfigureRelationships()
{
//Builder.HasOne(x => x.CreatedByUser)
// .WithMany()
// .HasForeignKey(x => x.CreatedByUserId)
// .IsRequired();
//Builder.HasOne<User>()
// .WithMany()
// .HasForeignKey(x => x.CreatedByUserId)
// .IsRequired();
}
public override void ConfigureIgnoredRelationships()
{
//Builder.Ignore(x => x.CreatedByUser);
//Builder.Ignore(x => x.CreatedByUserId);
}
}
TYPE CONFIGURATION - BASE TYPE
public abstract class BaseTypeConfiguration<TEntity, TKey> : IEntityTypeConfiguration<TEntity> where TEntity : class, IEntity<TKey>
{
private const string _COLUMN_ID = "id";
private readonly string _schemaName;
private readonly string _tableName;
public EntityTypeBuilder<TEntity> Builder { get; internal set; }
public BaseTypeConfiguration(string schemaName, string tableName)
{
_schemaName = schemaName;
_tableName = tableName;
}
public virtual void Configure(EntityTypeBuilder<TEntity> builder)
{
Builder = builder;
ConfigureSchema();
ConfigureTable();
ConfigureColumns();
ConfigureRelationships();
ConfigureIgnoredRelationships();
}
public virtual void ConfigureSchema()
{
Builder.Metadata.SetSchema(_schemaName);
}
public virtual void ConfigureTable()
{
Builder.ToTable(_tableName);
}
public virtual void ConfigureColumns()
{
Builder.HasKey(x => x.Id);
Builder.Property(x => x.Id)
.HasColumnName(_COLUMN_ID);
}
public virtual void ConfigureRelationships() { }
public virtual void ConfigureIgnoredRelationships() { }
}
Now for the real issue(s)...these are what I have tried...
With the type config like it is...only referencing the Id field, I had this working from an EF standpoint as far as I could tell. But I didn't have the encapsulation that DDD is supposed to provide my model. I was managing everything in a NetworkService class instead of inside the Network entity.
If I remove the created_by_user_id column completely and use pure DDD guidance (at least guidance is what I can gather), I run into the issue that the CreatedByUser property can't be filled out by the EF context...makes sense...the Network context doesn't understand "user". I know I can support this via stored proc, but I don't what to go that route as I would prefer it to be in code. I can also look the user up after and add to the ViewModel that is being returned, but that sucks to have to make 2 db calls.
If I leave the created by user id column, but I add the .HasOne on CreatedByUser, then when I run the migrations code, EF creates the User/etc tables in the context. Not sure if this even works to solve the issue, since it tries to create the user's tables on migration which is duplicate of what the identity context does so my migrations fail.
What am I supposed to do here? I have a hard biz requirement that I have to keep track of who created the event and that logically is the aspnet_user record.
I have read about things like Event sourcing models and such to pushes changes around in the system...like when a user is created in the Identity Domain fire an event and then create an appropriate entry in the network context...ie...separate table for network users. Do I have to do this to support DDD? Basically every table I want to have CreatedBy/UdpatedBy users on as we track all this data.
Seems like a lot of extra work to support.
I couldn't find a direct way to accomplish what I needed to do here, but I was able to come up with a workable solution. It wasn't an ideal solution, but it works.
I effectively used the concept from "issue #3" above. I created a new DbContext for EF that is solely responsible for the schema migrations. This EF context contains a DbSet for every table in the database and will be used only during the database migration. Each schema specific context is still used for database transactions in the system.
The reason this works is because the migration context now understands all the tables in the entire database and will not duplicate the "fringe" tables that sit on the border between the bounded contexts....ie...user in this case. The only real drawback I see here is the duplication of code to manage the same tables in 2 contexts. This still seems like a lot less work than creating slimmed down "user" tables in every context I create just so I can link to who the person was that created a row.
I recently started to work with EF and MVC in .Net Web Applications, and I've encountered an issue, and wanted to see what your thoughts were and whether you would be able to point me in the right direction.
So, I've got two classes: DataGroup and DataElements. A DataElement can be in one DataGroup or none. I've used fluent API in the DataContext to achieve this:
modelBuilder.Entity<DataGroup>()
.HasMany(dg => dg.DataElements)
.WithOptional()
.HasForeignKey(de => de.DataGroup_Id);
In my DataGroup class I've got the following:
public virtual ICollection<DataElement> DataElements { get; set; }
[NotMapped]
public ICollection<DataElement> RecentDataElements //returns the last 15 data elements
{
get
{
return DataElements.OrderByDescending(de => de.GeneratedDateTime).Take(15).Reverse().ToList();
}
}
[NotMapped]
public DataElement LatestDataElement //returns the latest data element (if any)
{
return DataElements.OrderByDescending(de => de.GeneratedDateTime).FirstOrDefault();
}
So the issue I have with the code above is that when I call LatestDataElement or RecentDataElements, I end up waiting quite a bit.
I believe this is because the DataElements property in the DataGroup class is an ICollection and RecentDataElements and LatestDataElement end up causing EF to load all the DataElements into memory before sorting and returning a sub-set (there could be thousands of DataElements in a DataGroup).
Is there a way to make this more efficient?
I've toyed with the idea of querying the datacontext directly rather than using the DataElements property, but I wanted to see if there were any other options I should consider. I've been told by colleagues that it would be bad practice to put the datacontext in a model (whether they're right or wrong is a different issue).
Thank you for your help and advice. It's much appreciated :)
In short: No.
As the name of the NotMapped attribute already suggests, this is a property linq-to-entities has no knowledge of whatsoever and it's impossible to populate it by filtering on the database. What I would do in your place is remove the Notmapped properties from your model all together and create a viewmodel:
public class DataGroupViewModel
{
public DataGroup DataGroup {get; set;}
public ICollection<DataGroup> RecentDataElements {get; set;}
}
And use projection in your query to populate this view:
var result = ctx.DataGroups.Where(...).Select(d => new DataGroupViewModel
{
DataGroup = d;
RecentDataElements = d.DataElements.OrderByDescending(de => de.GeneratedDateTime)
.FirstOrDefault();
}
This of course forces you to create another model but it's the cleanest and fastest way to do it. Also your colleagues are right, it is a bad way to do db calls in your model, a model is a container for data, nothing more than that, it shouldn't have logic inside it to query the database.
To make it a bit cleaner you can write some extension methods you can reuse to get the models:
Func<IQueryable<DataGroup>,IEnumerable<DataGroupViewModel>> GetDataGroupDTO =
d => d.Select(dt => new DataGroupViewModel
{
DataGroup = dt;
RecentDataElements = dt.DataElements.OrderByDescending(de => de.GeneratedDateTime)
.FirstOrDefault();
}
Then you can write your query more clean:
IEnumerable<DataGroupViewModel> result = ctx.DataGroups.Where(...).GetDataGroupDTO();
I have a very simple object models.
public class Contact
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Device Device { get; set; }
public virtual IList<string> Numbers { get; set; }
public Contact()
{
Numbers = new System.Collections.Generic.List<string>(3);
}
}
As you can see, the class Contact has an association with Numbers, which is a list of strings.
Here's the mapping:
Id(x => x.Id).GeneratedBy.Assigned();
Map(x => x.Name);
References(x => x.Device, "DeviceId");
Table("Contacts");
HasMany(x => x.Numbers)
.Table("ContactNumbers")
.Element("Number")
.KeyColumn("ContactId")
.LazyLoad()
.Cascade.All()
.Not
.Inverse();
Note that I can't and don't want the collection to be inverse=true, because it's just a collection of string. This means that Contact is responsible for updating Numbers entries.
Now my problem is that, whenever I try to add a new number to an existing Contact, it deletes all associated numbers and recreates them individually. Isn't NHibernate smart enough to detect changes and update only changed items?
I think there should be a simple solution for my problem but don't know what.
Any help would be appreciated.
This is actually documented in NHibernate's documentation.
Bags are the worst case. Since a bag permits duplicate element values
and has no index column, no primary key may be defined. NHibernate has
no way of distinguishing between duplicate rows. NHibernate resolves
this problem by completely removing (in a single DELETE) and
recreating the collection whenever it changes. This might be very
inefficient.
Try using an <idbag> mapping instead, and create a surrogate primary key for that table. Unfortunately, looks like <idbag> is not yet supported in FluentNHibernate.
Also, take a look at other collection mapping options.
I'm having problems setting up an Entity Framework 4 model.
A Contact object is exposed in the database as an updateable view. Also due to the history of the database, this Contact view has two different keys, one from a legacy system. So some other tables reference a contact with a 'ContactID' while other older tables reference it with a 'LegacyContactID'.
Since this is a view, there are no foreign keys in the database, and I'm trying to manually add associations in the designer. But the fluent associations don't seem to provide a way of specifying which field is referenced.
How do I build this model?
public class vwContact
{
public int KeyField { get; set; }
public string LegacyKeyField { get; set; }
}
public class SomeObject
{
public virtual vwContact Contact { get; set; }
public int ContactId { get; set; } //references vwContact.KeyField
}
public class LegacyObject
{
public virtual vwContact Contact { get; set; }
public string ContactId { get; set; } //references vwContact.LegacyKeyField
}
ModelCreatingFunction(modelBuilder)
{
// can't set both of these, right?
modelBuilder.Entity<vwContact>().HasKey(x => x.KeyField);
modelBuilder.Entity<vwContact>().HasKey(x => x.LegacyKeyField);
modelBuilder.Entity<LegacyObject>().HasRequired(x => x.Contact).???
//is there some way to say which key field this reference is referencing?
}
EDIT 2: "New things have come to light, man" - His Dudeness
After a but more experimentation and news, I found using a base class and child classes with different keys will not work by itself. With code first especially, base entities must define a key if they are not explicitly mapped to tables.
I left the suggested code below because I still recommend using the base class for your C# manageability, but I below the code I have updated my answer and provided other workaround options.
Unfortunately, the truth revealed is that you cannot accomplish what you seek without altering SQL due to limitations on EF 4.1+ code first.
Base Contact Class
public abstract class BaseContact
{
// Include all properties here except for the keys
// public string Name { get; set; }
}
Entity Classes
Set this up via the fluent API if you like, but for easy illustration I've used the data annotations
public class Contact : BaseContact
{
[Key]
public int KeyField { get; set; }
public string LegacyKeyField { get; set; }
}
public class LegacyContact : BaseContact
{
public int KeyField { get; set; }
[Key]
public string LegacyKeyField { get; set; }
}
Using the Entities
Classes that reference or manipulate the contact objects should reference the base class much like an interface:
public class SomeCustomObject
{
public BaseContact Contact { get; set; }
}
If later you need to programmatically determine what type you are working with use typeof() and manipulate the entity accordingly.
var co = new SomeCustomObject(); // assume its loaded with data
if(co.Contact == typeof(LegacyContact)
// manipulate accordingly.
New Options & Workarounds
As I suggested in comment before, you won't be able to map them to a single view/table anyway so you have a couple options:
a. map your objects to their underlying tables and alter your "get/read" methods on repositories and service classes pull from the joined view -or-
b. create a second view and map each object to their appropriate view.
c. map one entity to its underlying table and one to the view.
Summary
Try (B) first, creating a separate view because it requires the least amount of change to both code and DB schema (you aren't fiddling with underlying tables, or affecting stored procedures). It also ensures your EF C# POCOs will function equivalently (one to a view and one to table may cause quirks). Miguel's answer below seems to be roughly the same suggestion so I would start here if it's possible.
Option (C) seems worst because your POCO entities may behave have unforseen quirks when mapped to different SQL pieces (tables vs. views) causing coding issues down the road.
Option (A), while it fits EF's intention best (entities mapped to tables), it means to get your joined view you must alter your C# services/repositories to work with the EF entities for Add, Update, Delete operations, but tell the Pull/Read-like methods to grab data from the joint views. This is probably your best choice, but involves more work than (B) and may also affect Schema in the long run. More complexity equals more risk.
Edit I'm not sure this is actually possible, and this is why:
The assumption is that a foreign key references a primary key. What you've got is two fields which are both acting as primary keys of vwContact, but depending on which object you ask it's a different field that's the primary key. You can only have one primary key at once, and although you can have a compound primary key you can't do primary key things with only half of it - you have to have a compound foreign key with which to reference it.
This is why Entity Framework doesn't have a way to specify the mapping column on the target side, because it has to use the primary key.
Now, you can layer some more objects on top of the EF entities to do some manual lookup and simulate the navigation properties, but I don't think you can actually get EF to do what you want because SQL itself won't do what you want - the rule is one primary key per table, and it's not negotiable.
From what you said about your database structure, it may be possible for you to write a migration script which can give the contact entities a consistent primary key and update everything else to refer to them with that single primary key rather than the two systems resulting from the legacy data, as you can of course do joins on any fields you like. I don't think you're going to get a seamlessly functional EF model without changing your database though.
Original Answer That Won't Work
So, vwContact contains a key KeyField which is referenced by many SomeObjects and another key LegacyKeyField which is referenced by many LegacyObjects.
I think this is how you have to approach this:
Give vwContact navigation properties for SomeObject and LegacyObject collections:
public virtual ICollection<SomeObject> SomeObjects { get; set; }
public virtual ICollection<LegacyObject> LegacyObjects { get; set; }
Give those navigation properties foreign keys to use:
modelBuilder.Entity<vwContact>()
.HasMany(c => c.SomeObjects)
.WithRequired(s => s.Contact)
.HasForeignKey(c => c.KeyField);
modelBuilder.Entity<vwContact>()
.HasMany(c => c.LegacyObjects)
.WithRequired(l => l.Contact)
.HasForeignKey(c => c.LegacyKeyField);
The trouble is I would guess you've already tried this and it didn't work, in which case I can't offer you much else as I've not done a huge amount of this kind of thing (our database is much closer to the kinds of thing EF expects so we've had to do relatively minimal mapping overrides, usually with many-to-many relationships).
As for your two calls to HasKey on vwContact, they can't both be the definitive key for the object, so it's either a compound key which features both of them, or pick one, or there's another field you haven't mentioned which is the real primary key. From here it's not really possible to say what the right option there is.
You should be able to do this with two different objects to represent the Contact view.
public class vwContact
{
public int KeyField { get; set; }
public string LegacyKeyField { get; set; }
}
public class vwLegacyContact
{
public int KeyField { get; set; }
public string LegacyKeyField { get; set; }
}
public class SomeObject
{
public virtual vwContact Contact { get; set; }
public int ContactId { get; set; } //references vwContact.KeyField
}
public class LegacyObject
{
public virtual vwLegacyContact Contact { get; set; }
public string ContactId { get; set; } //references vwLegacyContact.LegacyKeyField
}
ModelCreatingFunction(modelBuilder)
{
// can't set both of these, right?
modelBuilder.Entity<vwContact>().HasKey(x => x.KeyField);
modelBuilder.Entity<vwLegacyContact>().HasKey(x => x.LegacyKeyField);
// The rest of your configuration
}
I have tried everything that you can imagine, and found that most solutions won't work in this version of EF... maybe in future versions it supports referencing another entity by using an unique field, but this is not the case now. I also found two solutions that work, but they are more of a workaround than solutions.
I tried all of the following things, that didn't work:
Mapping two entities to the same table: this is not allowed in EF4.
Inheriting from a base that has no key definitions: all root classes must have keys, so that inherited classes share this common key... that is how inheritance works in EF4.
Inheriting from base class that defines all fields, including keys, and then use modelBuilder to tell wich base-properties are keys of the derived types: this doesn't work, because the methos HasKey, Property and others that take members as parameters, must reference members of the class itself... referencing properties of a base class is not allowed. This cannot be done: modelBuilder.HasKey<MyClass>(x => x.BaseKeyField)
The two things that I did that worked:
Without DB changes: Map to the table that is source of the view in question... that is, if vwContact is a view to Contacts table, then you can map a class to Contacts, and use it by setting the key to the KeyField, and another class mapping to the vwContacts view, with the key being LegacyKeyField. In the class Contacts, the LegacyKeyField must exist, and you will have to manage this manually, when using the Contacts class. Also, when using the class vwContacts you will have to manually manage the KeyField, unless it is an autoincrement field in the DB, in this case, you must remove the property from vwContacts class.
Changing DB: Create another view, just like the vwContacts, say vwContactsLegacy, and map it to a class in wich the key is the LegacyKeyField, and map vwContacts to the original view, using KeyField as the key. All limitations from the first case also applies: the vwContacts must have the LegacyKeyField, managed manually. And the vwContactsLegacy, must have the KetField if it is not autoincrement idenitity, otherwise it must not be defined.
There are some limitations:
As I said, these solutions are work-arounds... not real solutions, there are some serious implications, that may even make them undesirable:
EF does not know that you are mapping two classes to the same thing. So when you update one thing, the other one could be changed or not, it depends if the objects is cached or not. Also, you could have two objects at the same time, that represents the same thing on the backing storage, so say you load a vwContact and also a vwContactLegacy, changes both, and then try to save both... you will have to care about this yourself.
You will have to manage one of the keys manually. If you are using vwContacts class, the KeyFieldLegacy is there, and you must fill it. If you want to create a vwContacts, and associate is with a LegacyObject, then you need to create the reference manually, because LegacyObject takes a vwContactsLegacy, not a vwContacts... you will have to create the reference by setting the ContactId field.
I hope that this is more of a help than a disillusion, EF is a powerfull toy, but it is far from perfect... though I think it's going to get much better in the next versions.
I think this may be possible using extension methods, although not directly through EF as #Matthew Walton mentioned in his edit above.
However, with extension methods, you can specify what to do behind the scenes, and have a simple call to it.
public class LegacyObject
{
public virtual vwContact Contact { get; set; }
public string ContactId { get; set; } //references vwContact.LegacyKeyField
}
public class LegacyObjectExtensions
{
public static vwContact Contacts(this LegacyObject legacyObject)
{
var dbContext = new LegacyDbContext();
var contacts = from o in legacyObject
join c in dbContext.vwContact
on o.ContactId == c.LegacyKeyField
select c;
return contacts;
}
}
and
public class SomeObject
{
public virtual vwContact Contact { get; set; }
public int ContactId { get; set; } //references vwContact.KeyField
}
public class SomeObjectExtensions
{
public static vwContact Contacts(this SomeObject someObject)
{
var dbContext = new LegacyDbContext();
var contacts = from o in someObject
join c in dbContext.vwContact
on o.ContactId == c.KeyField
select c;
return contacts;
}
}
Then to use you can simply do like this:
var legacyContacts = legacyObject.Contacts();
var someContacts = someObject.Contacts();
Sometimes it makes more sense to map it from the other end of the relationship, in your case:
modelBuilder.Entity<LegacyObject>().HasRequired(x => x.Contact).WithMany().HasForeignKey(u => u.LegacyKeyField);
however this will require that u.LegacyKeyField is marked as a primary key.
And then I'll give my two cents:
if the Legacy db is using LegacyKeyField, then perhaps the legacy db will be read only. In this case we can create two separate contexts Legacy and Non-legacy and map them accordingly. This can potentially become a bit messy as you'd have to remember which object comes from which context. But then again, nothing stops you from adding the same EF code first object into 2 different contexts
Another solution is to use views with ContactId added for all other legacy tables and map them into one context. This will tax performance for the sake of having cleaner context objects, but this can be counteracted on sql side: indexed views, materialized views, stored procs, etc. So than LEGACY_OBJECT becomes VW_LEGACY OBJECT with CONTACT.ContactId brought over, then:
modelBuilder.Entity<LegacyObject>().ToTable("VW_LEGACY_OBJECT");
modelBuilder.Entity<LegacyObject>().HasRequired(x => x.Contact).WithMany().HasForeignKey(u => u.ContactId);
I personally would go with creating "mapper views" with CustomerId on legacy tables, as it's cleaner from c# layer perspective and you can make those views look like real tables. It is also difficult to suggest a solution without knowing what exactly is the scenario that you have a problem with: querying, loading, saving, etc.
my application has the following entity:
public class User
{
public virtual int UserID { get; set; }
public virtual Membership LatestMembership { get { return Membership.First(); } }
public virtual IList<Membership> Membership { get; set; }
public User()
{
Membership = new List<Membership>();
}
}
With the following mapping:
public UserMap()
{
Table("Users");
Id(x => x.UserID);
HasMany(x => x.Membership)
.KeyColumn("UserID")
.OrderBy("DateAdded DESC")
.Inverse()
.Cascade.All();
}
The LatestMembership property against the user simply grabs the first record from the Membership collection (which is ordered so that the newer records are at the top).
So far so good, however now say i want to do the following (i know this will return them all but i'm just using this as an example):
var users = session.Linq<User>()
.Where(u => u.LatestMembership.DateAdded < DateTime.UtcNow);
An error is thrown because the LatestMembership property is beyond the nhibernate linq providers capabilities. The only solution i have so far is to convert it to a list and then apply the where condition but i'd imagine this could become pretty insuficient for a large database.
I was wondering if there was an alternative way i could map this or what your recommendations are. Thanks
Unfortunately, this isn't really possible using the current Linq provider. One thing you can do is to map the properties of the LatestMembership you want to query on as read-only and use formulas in your mappings to derive the values for it. You can see some further details in my answer to this post.
The other possible solution is to attack this from the Membership side of things; query for the latest membership matching the user in question and filter accordingly.