How to share EF6 objects in code-first - c#

I am just starting to teach myself Entity Framework (6) from the code-first scenario. My problem is I don't understand how to correctly instruct EF that certain entities may be shared among other entities.
Here's a very simple scenario with a three-level tree structure. Class A objects owns class B objects, which owns, or references, class C objects:
public class A
{
[Key]
public string Id { get; set; }
public ICollection<B> Bees { get; set; }
public override string ToString() { return Id; }
}
public class B
{
[Key]
public string Id { get; set; }
public ICollection<C> Cees { get; set; }
public override string ToString() { return Id; }
}
public class C
{
[Key]
public string Id { get; set; }
public override string ToString() { return Id; }
}
I now create a scenario where a single A object owns five B objects which, in turn, shares a collection of five C objects. Here's the DbContext I've used:
public class Context : DbContext
{
public DbSet<A> As { get; set; }
public DbSet<C> Cs { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// SHOULD I INSTRUCT EF HOW TO HANDLE THIS HERE?
base.OnModelCreating(modelBuilder);
}
public Context(string nameOrConnectionString)
: base(nameOrConnectionString)
{
}
}
And here's some simple code to set up this scenario:
internal class Program
{
private static void Main(string[] args)
{
var context = new Context(#"Data Source=(localdb)\v11.0; Initial Catalog=TestEF6.Collections; Integrated Security=true");
var sharedCees = createCees("A1.B1", 5, context);
context.As.Add(new A { Id = "A1", Bees = createBees("A1", 5, sharedCees) });
context.SaveChanges();
}
private static ICollection<B> createBees(string aId, int count, ICollection<C> cees = null)
{
var list = new List<B>();
for (var i = 0; i < count; i++)
{
var id = aId + ".B" + (i + 1);
list.Add(new B { Id = id, Cees = cees ?? createCees(id, count) });
}
return list;
}
private static ICollection<C> createCees(string bId, int count, Context saveInContext = null)
{
var list = new List<C>();
for (var i = 0; i < count; i++)
{
var id = bId + ".C" + (i+1);
var c = new C {Id = id};
list.Add(c);
if (saveInContext != null)
saveInContext.Cs.Add(c);
}
if (saveInContext != null)
saveInContext.SaveChanges();
return list;
}
}
As EF creates the database ('TestEF6.SharedEntities') it assumes that A -> B, as well as B -> C are one-to-many relationships and that the child entities are components, fully owned and controlled by its master entity.
So, how do I make EF "see" that instances of C can be shared by instances of B?
In all honesty, I'm asking to get a quick start into EF. I do realize I can plow through the web and figure this out but if anyone can point me in the right direction I would very much appreciate it.
Cheers

Gert set me on track by suggesting I'd add a collection property to the C class. Indeed, this is enough for EF/CF to automatically recognize the many-to-many relationship between B and C. But the whole point with code-first is to design a domain model of classes and then have EF persist them, without having to bog them down with peristency information.
I have no issues with annotating Key properties, or even Required ones. This kind of information actually makes sense in the domain model. They can be used by the domain code, but even if they're not they add clarityto the code. But to add properties that serves no other function than to act as hints to EF's auto-magic didn't seem like a very elegant solution.
So I experimented some more with the fluent API and realized I had missed the parameterless overload of .WithMany(). I was looking for a generic form, that would make it possible to specify the other type of a many-to-many relationship. In my mind I would then write: modelBuilder.Entity<B>.HasMany(b => b.Cees).WithMany<B>(). As it turned out, the parameterless overload of WithMany() serves this exact purpose, as type C in this scenario is already specified by the first HasMany() part.
So, to summarize, EF will recognize the many-to-many relationship between classes B and C, without the need to "litter" the C clas with an extra generic collection of type B items. So, this is how we can describe the many-to-many relationsship between the two classes without having to touch the domain classes at all:
public class Context : DbContext
{
public DbSet<A> As { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<B>().HasMany(b => b.Cees).WithMany(); // <-- THE SOLUTION
base.OnModelCreating(modelBuilder);
}
public Context(string nameOrConnectionString)
: base(nameOrConnectionString)
{
}
}
I realize this is very basic stuff to anyone seasoned in EF but I'll go ahead and mark this as an answer so that others may save some time looking for this information in the future.
Thanks

Related

Duplicate record with ef6 when child object saved

I'm having a rather interesting problem and I am not quite sure how to properly get past it. In order to fully understand my problem, please keep in mind the following:
I am "Modulizing" my features. For example I have written a "Logger" dll that is then turned into a package. This DLL has its own DbContext and knows about certain tables as a result. Then I have written a "Tracker" dll which extends the Logger dll. The tracker dll is another module with its own Db Context and its own tables. It knows about the Logger dll only in the fact that it knows about its service layer and its model layer. Let me show you what that looks like:
Here are the Models (representing tables)
//Logger Module
public class LogError : ILogError
{
public Guid Id { get; set; }
//more stuff not relavent to the problem
}
//Tracker Module
public class ErrorTicket : IErrorTicket
{
public Guid Id { get; set; }
public Guid LogErrorId { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int TicketNumber { get; set; }
//More properties not related to the problem
public virtual LogError LogError { get; set; }
public virtual ILogError MyLogError => LogError;
public virtual ICollection<ErrorTicketNote> ErrorTicketNotes { get; set; }
public virtual IEnumerable<IErrorTicketNote> MyErrorTicketNotes => ErrorTicketNotes;
}
Please keep in mind for the ErroTicket Class, I am using interfaces to expose certain methods. For example my interface only has getters and no setters, so when an interface is passed, the class cannot be updated. I would rather not go into a discussion as to why I do that as I am very certain it is not part of the problem. Just wanted to make a not so you understand why I have LogError and then MyLogError listed up there.
Now for my DbContext I have the following:
//Logger Module
public class LoggerDbContext : DbContext, ILoggerDbContext
{
public DbSet<Model.LogError> LogError { get; set; }
public DbSet<Model.LogInfo> LogInfo { get; set; }
public IEnumerable<ILogError> LogErrors => LogError;
public IEnumerable<ILogInfo> LogInfos => LogInfo;
public LoggerDbContext(string connectionString = "DefaultConnection") : base(connectionString) { }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
base.OnModelCreating(modelBuilder);
}
public void CreateLog(ILogError logError)
{
LogError.Add((Model.LogError) logError);
SaveChanges();
}
public void CreateLog(ILogInfo logInfo)
{
LogInfo.Add((Model.LogInfo) logInfo);
SaveChanges();
}
}
//Tracker Module
public class TrackerDbContext : DbContext, ITrackerDbContext
{
public DbSet<ErrorTicket> ErrorTicket { get; set; }
public DbSet<ErrorTicketNote> ErrorTicketNote { get; set; }
public IEnumerable<IErrorTicket> ErrorTickets => ErrorTicket;
public IEnumerable<IErrorTicketNote> ErrorTicketNotes => ErrorTicketNote;
public TrackerDbContext(string connectionString = "DefaultConnection") : base(connectionString) { }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
base.OnModelCreating(modelBuilder);
}
public void CreateTicket(IErrorTicket errorTicket)
{
ErrorTicket.Add((ErrorTicket) errorTicket);
SaveChanges();
}
public void ModifyTicket(IErrorTicket errorTicket)
{
Entry(errorTicket).State = EntityState.Modified;
SaveChanges();
}
public void CreateTicketNote(IErrorTicketNote errorTicketNote)
{
ErrorTicketNote.Add((ErrorTicketNote) errorTicketNote);
SaveChanges();
}
public void ModifyTicketNote(IErrorTicketNote errorTicketNote)
{
Entry(errorTicketNote).State = EntityState.Modified;
SaveChanges();
}
}
As you can see two DbContext classes do not know about each other, but through my models I create a relation of foreign key. Now to my problem.
When an error occurs I have code that runs the following:
//the line bellow calls a Logger service that ends up invoking the method from the Logger DbContext - public void CreateLog(ILogError logError)
var var logError = _databaseLoggerService.Error(exception, message);
//Then I try to create my ErrorTicket and I assign the logError object to the class to create the relation of the foreign key.
var currentTime = DateTime.Now;
var errorTicket = new ErrorTicket
{
Id = Guid.NewGuid(),
LogErrorId = logError.Id,
TimeCreated = currentTime,
TimeResolved = null,
TimeOfFirstOccurrence = currentTime,
TimeOfLastOccurrence = currentTime,
TotalOccurrences = 1,
Resolved = false,
Resolution = string.Empty,
CommitNumber = string.Empty,
TimeImplemented = null,
LogError = (LogError) logError
};
_trackerDbContext.CreateTicket(errorTicket);
The problem that I get is the following:
Violation of PRIMARY KEY constraint 'PK_dbo.LogError'. Cannot insert duplicate key in object 'dbo.LogError'. The duplicate key value is (769fb127-a8d8-40de-9492-fc61ca86cb16).
The statement has been terminated.
If I look at my LogError table, a record indeed exists with that key. I assume it was created when I called _databaseLoggerService.Error(exception, message);
What I do not understand, is why is this a problem for EF6 or how to get past it?
I have done a lot of research on the topic and I have found articles which state that because it is 2 sepparate DbContextes the second one may not know that the record exist so it when I caled .Add method, it marked ALL objects for insertoin and thus generated the INSERT queries. Which makes sense, and I could simply not call my db creation and let my tracker just create both objects for me. Which is all fine, however, the problem I have with that is when I try to modify 'the record, I get the exact same problem. Even though I marked the record as modify, it generates the insert queries.
My question is how do I get past this problem?

Get ignored properties in Entity Framework

I work on a framework with EF. I want to get all ignored properties of an entity to build some special queries. How can I do it?
public class Customer
{
public int Id { get; set; }
public DateTime BirthDate { get; set; }
public int Age { get; set; }
}
public class CustomerContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>().Ignore(customer => customer.Age);
base.OnModelCreating(modelBuilder);
}
public DbSet<Customer> Customers { get; set; }
}
public static class DbContextExtensions
{
public static List<string> GetIgnoredProperties(this DbContext context, string entityTypeName)
{
// ???
}
}
I know this is not answering your original question, and in my comments I mentioned that you should use reflection, but that was only because I read your question wrong.
Here is an alternative using reflection, for if you do not come right.
If you assign the [NotMapped] attribute to the properties on your class that you would like to ignore, you could possibly retrieve all [NotMapped] properties using reflection. Below is an example of how this could be achieved.
var resultArray = yourClassInstance.GetType().GetProperties()
.Where(prop => Attribute.IsDefined(prop, typeof(NotMappedAttribute)));
Hope this helps you in some way.
You can achieve what you want by calling the DbModelBuilder.Build. It will create a DbModel base on configuration setup by the DbModelBuilder. The DbModel expose a ConceptualModel that hold the types used by the context. The EdmModel hold each type that are declared in the context, and for each type, it hold the properties that has not been ignored by the DbModelBuilder during it's configuration. So, to achieve what you want, you have to intersect the properties of each entity type with those present in the EdmModel. It will give the delta between them, thefore the ignored properties. Here an example :
public class CustomerContext : DbContext
{
private static IReadOnlyDictionary<Type, IReadOnlyCollection<PropertyInfo>> _ignoredProperties;
/// Hold the ignored properties configured from fluent mapping
public static IReadOnlyDictionary<Type, IReadOnlyCollection<PropertyInfo>> IgnoredProperties
{
get
{
return _ignoredProperties;
}
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>().Ignore(customer => customer.Age);
// Build ignored properties only if they are not
if (_ignoredProperties == null)
{
var model = modelBuilder.Build(this.Database.Connection);
var mappedEntityTypes = new Dictionary<Type, IReadOnlyCollection<PropertyInfo>>();
foreach (var entityType in model.ConceptualModel.EntityTypes)
{
var type = Type.GetType(entityType.FullName);
var typeProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
var mappedProperties = entityType.DeclaredProperties.Select(t => t.Name)
.Union(entityType.NavigationProperties.Select(t => t.Name));
mappedEntityTypes.Add(type, new ReadOnlyCollection<PropertyInfo>(
typeProperties.Where(t => !mappedProperties.Contains(t.Name)).ToList()));
}
_ignoredProperties = new ReadOnlyDictionary<Type, IReadOnlyCollection<PropertyInfo>>(mappedEntityTypes);
}
base.OnModelCreating(modelBuilder);
}
public DbSet<Customer> Customers { get; set; }
}
The IgnoreProperties property is a singleton that will be initialized the first time you will use the context. It will be null before that, so will have to ensure that nothing use it until it's initialized. It's readonly, so you don't have to worrie about accidental clear of the collection. The entity type is used as key, and the value expose a collection that hold ignored properties. Example of use :
var properties = CustomerContext.IgnoredProperties[typeof(Customer)];
Cons :
With this approach is that the DbModel will be built twice, one time to gather the ignored properties, and second time by EntityFramework when the DbCompiledModel will be cached for futur ObjectContext creation. It can have an impact on the cold start of the DbContext, it means that the fist time you will execute a query over your context, it will be a bit slower. It will depend on the size of the DbContext. Warm queries should not suffer. OnModelCreating will be called once anyway.
Pros :
All changes made on de DbModelBuilder configuration will be automatically reflected in the IgnoredProperties property.

Entity Framework entity state management Confusion

I have used EF (now with 6.1.1) for a relevant time. But every time I need to work with more than one entity inside a controller (using MVC) I lose myself.
The state management of entities causes me great confusion and when I think I understand the operation then comes another surprise.
For example (my current confusion):
I have the following classes:
public class A
{
public int Id { get; set; }
public C c { get; set; }
}
public class B
{
public int Id { get; set; }
public C c { get; set; }
}
public class C
{
public int Id { get; set; }
public string anotherProperty { get; set; }
}
And I have a Controller:
[HttpPost]
public void CopyEntityAction(A a){
var b = new B() {
c = a.c // <== here is the problem
};
db.Bs.Add(b);
}
What I want to do is copy (create another entity) C from A to B, and not link to the same C.Id
How can I achieve this?
Thanks for your time
* I think somethings EF could take care automatically
Edit 1
I tried this too:
[HttpPost]
public void CopyEntityAction(A a){
var b = new B();
var c2 = a.c;
db.Entry(c2).State = EntityState.Added;
b.c = c2;
db.Bs.Add(b);
db.SaveChanges();
}
I think you need something like this. Otherwise you are dealing with the same exact C object, not creating a new one.
var b = new B() {
c = new C { Id = a.c.Id, anotherProperty = a.c.Anotherproperty } // <== here is the problem
};
You may also need to explicitly need to add the new C to the context's C collection.
It's really not all that confusing. It boils down to where that data comes from. When you retrieve an object from Entity Framework it is "attached" to your context. However, when you post data to an action and have the modelbinder new up an instance of your entity with that data (via including it as a parameter), it is not attached. That means Entity Framework knows nothing about this object. It doesn't know where it came from, whether it's ever been saved before or not (is this going to be an update or an insert?), etc. So, it's on you to tell it what to do at that point. That is what the EntityState enum is for.

Fluent NHibernate M2M Mapping ([One] to [Many - Many] to [One])

i have searched the "whole" internetz for this question, and its a damn hard one to search for as its rather complicated. Try searching for "Fluent NHibernate Many to Many with a bridge table with extra columns" etc...
Okay, to make it easier to explain ill define some tables i can refer to.
Table: User, Table: Function, Table: User_Has_Function.
One User can have many Functions, and a Function can have many Users, this is linked in the bridge table User_Has_Function. The bridge table has extra columns which is only relevant to the relationship.
Well anyways iv found that FNH doesn't have any automatic solution to this, basically you have to use a one to many relation from User to User_Has_Function and many to one from User_Has_Function to Function, hence "[One] to [Many - Many] to [One]".
I have solved it like in this link http://sessionfactory.blogspot.com/2010/12/many-to-many-relationships-with.html just with FNH class mapping instead of xml obviously.
But im not satisfied with the solution, do i really have to do all this manually work to make this function properly? Also as it is now it inserts duplicates in the bridge table.
In my head i'm doing something wrong, cause i cant imagine there is no support for this. Just use SaveAndUpdate(), no duplicates are inserted and when i remove an entity the relation is removed as well, if no relations are left remove the entity itself etc.
Okay here are my entities and mappings, I am VERY new to Fluent NHibernate so don't yell to much if i have done something very wrong. :)
Entities:
public class XUser
{
public virtual int Id { get; set; }
...
public virtual IList<XUserHasXFunction> XUserHasXFunctions { get; set; }
public XUser()
{
XUserHasXFunctions = new List<XUserHasXFunction>();
}
public virtual void AddXFunction(XFunction xFunction, int isActive)
{
var xUserHasXFunction = new XUserHasXFunction()
{
XUser = this,
XFunction = xFunction,
DeployedDate = DateTime.Now
};
XUserHasXFunctions.Add(xUserHasXFunction);
xFunction.XUserHasXFunctions.Add(xUserHasXFunction);
}
public virtual void RemoveXFunction(XFunction xFunction)
{
var xUserHasXFunction = XUserHasXFunctions.Single(x => x.XFunction == xFunction);
XUserHasXFunctions.Remove(xUserHasXFunction);
xFunction.XUserHasXFunctions.Remove(xUserHasXFunction);
}
}
public class XFunction
{
public virtual int Id { get; set; }
...
public virtual IList<XUserHasXFunction> XUserHasXFunctions { get; set; }
public XFunction()
{
XUserHasXFunctions = new List<XUserHasXFunction>();
}
public virtual void AddXUser(XUser xUser, int isActive)
{
var xUserHasXFunction = new XUserHasXFunction()
{
XUser = xUser,
XFunction = this,
DeployedDate = DateTime.Now
};
XUserHasXFunctions.Add(xUserHasXFunction);
xUser.XUserHasXFunctions.Add(xUserHasXFunction);
}
public virtual void RemoveXUser(XUser xUser)
{
var xUserHasXFunction = XUserHasXFunctions.Single(x => x.XUser == xUser);
XUserHasXFunctions.Remove(xUserHasXFunction);
xUser.XUserHasXFunctions.Remove(xUserHasXFunction);
}
}
public class XUserHasXFunction
{
public virtual int Id { get; set; }
public virtual XUser XUser { get; set; }
public virtual XFunction XFunction { get; set; }
public virtual DateTime DeployedDate { get; set; }
}
Mappings:
public class XUserMap : ClassMap<XUser>
{
public XUserMap()
{
Id(x => x.Id, "ID").GeneratedBy.Sequence("SEQ").Column("ID");
Table("XUSER");
...
HasMany(x => x.XUserHasXFunctions).Cascade.All();
}
}
public class XFunctionMap : ClassMap<XFunction>
{
public XFunctionMap()
{
Id(x => x.Id, "ID").GeneratedBy.Sequence("SEQ").Column("ID");
Table("XFUNCTION");
...
HasMany(x => x.XUserHasXFunctions).Cascade.All();
}
}
public class XUserHasXFunctionMap : ClassMap<XUserHasXFunction>
{
public XUserHasXFunctionMap()
{
Id(x => x.Id, "ID").GeneratedBy.Sequence("SEQ").Column("ID");
Table("USER_HAS_FUNCTION");
Map(x => x.DeployedDate, "DEPLOYED_DATE");
References(x => x.XUser).ForeignKey("XUSER_ID").Cascade.SaveUpdate();
References(x => x.XFunction).ForeignKey("XFUNCTION_ID").Cascade.SaveUpdate();
}
}
I don't understand the "do i really have to do all this manual work" part. What "all this manual work"? There is nothing special there. The mapping is simple and the c# code doesn't have to do anything with persistency, it's plain old OO design.
If you get duplicated rows, there is something wrong with your mapping. It might be because of a inverse collection which had not been mapped as inverse.
If you don't need to navigate from Function to User, it's very easy. Either map the relation as entity, as described in the blog, or even easier, map it as a composite element.
(Sorry, I don't know Fluent)
<bag name="Functions" table="User_Has_Function">
<key column="UserId" />
<composite-element>
<many-to-one class="Function"/>
</composite-element>
</bag>
Edit:
From the comments:
The manual work I am talking about is the manual getting and checking
to remove and add relations from a user or function.
Are you talking about the required Add and Remove methods, which maintain the consistency of the relations? This is plain OO design. If you hadn't NHibernate, you would have to write it exactly the same (given the same class model).
delete a user from a function make it cascade all the way to user and
so forth...
No. Delete-cascading happens when an object is deleted. When you delete a user, you should cascade the user_has_function. From there, you may or may not cascade the functions. The same in the other direction. There is also the concept of "cascade-all-delete-orphans". It means that additionally to regular cascading, an object is deleted automatically when it is removed from the collection. This is not cascading. It is a kind of very basic garbage collection. If you want to make use of this in your case, you should not apply it to both the user->user_has_function collection and the function->user_has_function collection, because it would try to delete the object twice.
Don't forget to map both collections inverse. If you don't, you may get duplicated entries.
Make sure that the three mappings (the user->user_has_function collection, the function->user_has_function and the user_has_function class mapping) are using the same table name and foreign key names.
You don't need to mess around with composite keys.
I ended up doing something similar a while ago with user, group, user_group and ended up having to use a hacky method of having both objects exist on both sides and also manually choose between save or update.
I don't think there is a NICE way to do what you want, and I agree it is something that from a database point of view is fairly logical to do, but from a modelling point of view is a pain.
As I also assume you are having to use a composite key for your user_has_function table to make sure that you can have multiple functions for multiple users. Which I think most people try to avoid and end up using surrogate keys or some other approach.
I know this isn't an answer, but I never found a real answer to the same question when I posted it.
Here is a similar question I posted a while back:
Nhibernate composite key question
I ended up using an ISet instead of having the relations in ILists. ISet does not allow duplicates, but IList does. To use ISet you have to override the Equals and GetHashCode methods for the object stored in the ISet.
I cascade from XUser and XFunction and not the other way around, ended up that every record in all 3 tables were deleted when i deleted one entity because of cascading.
Here is how i solved it.
Entities:
public class XUser
{
public virtual int Id { get; set; }
...
public virtual ISet<XUserHasXFunction> XUserHasXFunctions { get; set; }
public XUser()
{
XUserHasXFunctions = new HashedSet<XUserHasXFunction>();
}
public virtual void AddXFunction(XFunction xFunction, int isActive)
{
var xUserHasXFunction = new XUserHasXFunction()
{
XUser = this,
XFunction = xFunction,
IsActive = isActive,
DeployedDate = DateTime.Now
};
if (XUserHasXFunctions.Contains(xUserHasXFunction) && xFunction.XUserHasXFunctions.Contains(xUserHasXFunction))
{
return;
}
XUserHasXFunctions.Add(xUserHasXFunction);
xFunction.XUserHasXFunctions.Add(xUserHasXFunction);
}
public virtual void RemoveXFunction(XFunction xFunction)
{
var xUserHasXFunction = XUserHasXFunctions.Single(x => x.XFunction == xFunction);
XUserHasXFunctions.Remove(xUserHasXFunction);
xFunction.XUserHasXFunctions.Remove(xUserHasXFunction);
}
}
public class XFunction
{
public virtual int Id { get; set; }
...
public virtual ISet<XUserHasXFunction> XUserHasXFunctions { get; set; }
public XFunction()
{
XUserHasXFunctions = new HashedSet<XUserHasXFunction>();
}
public virtual void AddXUser(XUser xUser, int isActive)
{
var xUserHasXFunction = new XUserHasXFunction()
{
XUser = xUser,
XFunction = this,
IsActive = isActive,
DeployedDate = DateTime.Now
};
if (XUserHasXFunctions.Contains(xUserHasXFunction) && xUser.XUserHasXFunctions.Contains(xUserHasXFunction))
{
return;
}
XUserHasXFunctions.Add(xUserHasXFunction);
xUser.XUserHasXFunctions.Add(xUserHasXFunction);
}
public virtual void RemoveXUser(XUser xUser)
{
var xUserHasXFunction = XUserHasXFunctions.Single(x => x.XUser == xUser);
XUserHasXFunctions.Remove(xUserHasXFunction);
xUser.XUserHasXFunctions.Remove(xUserHasXFunction);
}
}
public class XUserHasXFunction
{
public virtual int Id { get; set; }
...
public virtual DateTime DeployedDate { get; set; }
public override bool Equals(object obj)
{
if (obj == null)
return false;
var t = obj as XUserHasXFunction;
if (t == null)
return false;
return XUser == t.XUser && XFunction == t.XFunction;
}
public override int GetHashCode()
{
return (XUser.Id + "|" + XFunction.Id).GetHashCode();
}
}
Mappings:
public class XUserMap : ClassMap<XUser>
{
public XUserMap()
{
Id(x => x.Id, "ID").GeneratedBy.Sequence("SEQ").Column("ID");
Table("XUSER");
...
HasMany(x => x.XUserHasXFunctions).KeyColumn("XUSER_ID").Cascade.All();
}
}
public class XFunctionMap : ClassMap<XFunction>
{
public XFunctionMap()
{
Id(x => x.Id, "ID").GeneratedBy.Sequence("SEQ").Column("ID");
Table("XFUNCTION");
...
HasMany(x => x.XUserHasXFunctions)KeyColumn("XFUNCTION_ID").Cascade.All();
}
}
public class XUserHasXFunctionMap : ClassMap<XUserHasXFunction>
{
public XUserHasXFunctionMap()
{
Id(x => x.Id, "ID").GeneratedBy.Sequence("SEQ").Column("ID");
Table("XUSER_HAS_XFUNCTION");
...
Map(x => x.DeployedDate, "DEPLOYED_DATE");
References(x => x.XUser).Column("XUSER_ID");
References(x => x.XFunction).Column("XFUNCTION_ID");
}
}
Usage:
To add relations.
xFunction.AddXUser(xUser, isActive); //visa versa if you like to add a function to a user...
dao.Store(xFunction); //to actually add the relation in the db
now to remove relation
xFunction.RemoveXUser(xUser); //Realtion is removed but neither of the objects xFunction or xUser
dao.Store(xFunction); //...same
to remove a user and its relations.
dao.delete(xUser); //but the xFunction object it was connected to is not removed
//if you want the xFunction object to be removed you have to do that manually.

Speeding Up Entity Framework 4.2 POCO

I am using the Entity Framework 4.2 and I have a fairly serious performance issue. I am using the POCO approach, inheriting from DbContext and here is a small sample that explains the problem:
I have a database that has 2 tables - A and B:
A
AId (int - not null - identity - primary key)
Name (nvarchar(50) - not null)
B
BId (int - not null - identity - primary key)
SomeValue (int - not null)
AId (int - not null - foreign key connecting to AId in the table A)
A has a single row in it (1, 'Test') and B has 6000 rows (SomeValue is just a number from 0 to 5999) - all of which reference the A row via the foreign key column.
I create an edmx from the database and turn off code generation. I then create the following classes:
public class DatabaseContext : DbContext
{
public DatabaseContext(string name) : base(name)
{
Configuration.AutoDetectChangesEnabled = false;
As = Set<A>();
Bs = Set<B>();
}
public DbSet<A> As { get; private set; }
public DbSet<B> Bs { get; private set; }
}
public class A
{
public virtual int AId { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<B> Bs { get; private set; }
public void AddB(B b)
{
if (b == null)
{
throw new ArgumentNullException("b");
}
if (Bs == null)
{
Bs = new List<B>();
}
if (!Bs.Contains(b))
{
Bs.Add(b);
}
b.A = this;
}
}
public class B
{
public virtual int BId { get; set; }
public virtual A A { get; set; }
public virtual int SomeValue { get; set; }
}
Now I simply do the following:
var ctx = new DatabaseContext("ScalabilityTestEntities");
var a = ctx.As.FirstOrDefault();
a.Bs.Add(new B { SomeValue = 987 });
The last line (where I add a new B) takes something in the region of 6 seconds on my quad core, 4gb RAM 64 bit Windows 7 machine that has the database running locally.
The really bad thing is that it seems to degrade something like exponentially since if you double the number of rows in B, it takes nearer to 20 seconds!
I would really appreciate any tips to make this happen faster. Thanks very much!
The world of navigation properties can be a painful one. We essentially had to phase out their use because they cause so many performance problems behind your back (especially when you get into attaching and detaching entities, but that's a different story).
What's happening is that when you access a.Bs it loads all of the B's for that A.
In this specific case, if you don't actually need the full list of B's and you just want to add a new one, it's better to simply create a B and set its AId to a's ID.
In such a case you should disable lazy loading:
var ctx = new DatabaseContext("ScalabilityTestEntities");
ctx.Configuration.LazyLoadingEnabled = false;
var a = ctx.As.FirstOrDefault();
a.Bs = new List<B>();
a.Bs.Add(new B { SomeValue = 987 });
You have lazy loading enabled by default (which causes all 6000 Bs to be loaded when you access the collection) because your navigation collection is declared as virtual. If you never need or want to use lazy loading you should remove the virtual keyword altogether or disable lazy loading in the context constructor.

Categories

Resources