This should be a simple one involving EF Code first but I can't wrap my head around the documentation and all the examples I am finding are from older versions. I am working with the latest (4.1).
Anyway I have some models like:
public class Foo
{
public int ID { get; set; }
public Bar Bar { get; set; }
}
public class Bar
{
public int ID { get; set; }
public string Value { get; set; }
}
I used some scaffolding with Asp.Net MVC to create my controllers/repositories and when I create a 'Foo' object, it also creates a 'Bar' object even though I set the 'Bar' property from something stored in the database.
public class FooViewModel
{
public int ID { get; set; }
public int BarID { get; set; }
}
public ActionResult Create(FooViewModel foo)
{
var entity = new Foo()
{
ID = foo.ID,
Bar = _barRepository.Find(foo.BarID)
};
_fooRepository.InsertOrUpdate(entity);
_fooRepository.Save();
// more stuff
}
How can I use fluent syntax for EF in order to stop it from creating a new 'Bar' row in the database?
Update
Here is the generated repository code:
public void InsertOrUpdate(Foo foo)
{
if (foo.ID == default(int)) {
// New entity
context.Foo.Add(foo);
} else {
// Existing entity
context.Foo(foo).State = EntityState.Modified;
}
}
public void Save()
{
context.SaveChanges();
}
your _fooRepository and _barRepository need to share same DB context instance. If the are using two instances the Bar will be in added state.
The problem must be somewhere in your repository layer - using the same model directly with EF 4.1 produces the expected result - a new row in the Foos table with a bar FK column pointing to the existing Bar.
Related
In my EF Core solution I have the following model:
public class Deal
{
public string Id { get; set; }
public ResponsiblePerson ResponsiblePerson1 { get; set; }
public ResponsiblePerson ResponsiblePerson2 { get; set; }
public ResponsiblePerson ResponsiblePerson3 { get; set; }
}
public class ResponsiblePerson
{
public string Id { get; set; }
public string Name { get; set; }
}
When I am trying to update Deal navigations properties:
private void UpdateResponsiblePersons(string dealId, string person1Id, string person2Id, string person3Id)
{
var existingdeal = _dbContext.Deals
.Include(d => d.ResponsiblePerson1)
.Include(d => d.ResponsiblePerson2)
.Include(d => d.ResponsiblePerson3)
.Single(d => d.Id == dealId);
existingDeal.ResponsiblePerson1 = new ResponsiblePerson { Id = person1Id };
existingDeal.ResponsiblePerson2 = new ResponsiblePerson { Id = person2Id };
existingDeal.ResponsiblePerson3 = new ResponsiblePerson { Id = person3Id };
_dbContext.Entry(deal.ResponsiblePerson1).State = EntityState.Unchanged;
_dbContext.Entry(deal.ResponsiblePerson3).State = EntityState.Unchanged;
_dbContext.Entry(deal.ResponsiblePerson3).State = EntityState.Unchanged;
_dbContext.SaveChanges();
}
EF often fails with
System.InvalidOperationException: The instance of entity type 'ResponsiblePerson' cannot be tracked because another instance with the key value '{Id: 1}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
That is because sometimes existingdeal already contains the link to ResponsiblePerson with one of provided IDs in either ResponsiblePerson1 of ResponsiblePerson2 or ResponsiblePerson3 Navigation properties.
I know that one of possible solutions will be first to get ResponsiblePersons used for update from dbContext like
existingDeal.ResponsiblePerson1 = _dbContext.ResponsiblePersons.Find(person1Id)
But that means extra DB roundtrips.
Another solution is to expose foreign keys instead of navigation properties but it would make Deal model quite ugly.
Please advice me what is the best way of updating such references?
I have the following Entity class definition:
[Table("Users")]
public class WebUser
{
public virtual int Id { get; set; }
public virtual ICollection<Client> Clients { get; set; }
// more properties...
}
Notice that table name is different than the class name. I also have a ClientUsers table which is a many-to-many mapping for clients and users. Problem is, when I try to access the webUser.Clients property I get the following exception:
"Invalid object name 'dbo.ClientWebUsers'."
Looks like Entity Framework is trying to guess the name of the third table, but it apparently was not smart enough to take into account the table attribute that I have there. How can I tell EF that it is ClientUsers and not ClientWebUsers? Also what rule does it follow to know which table name comes first and which one comes second in the new table name? I think it's not alphabetical order.
I'm using EF 5.0. Thanks!
From the looks of things you're using Code First, so I'll answer accordingly. If this is incorrect, please let me know.
I believe the convention being used to determine the name of the many-to-many table is determined by the order in which they occur as DbSet properties in your SomeContext : DbContext class.
As for forcing EntityFramework to name your table whatever you like, you can use the Fluent API in the OnModelCreating method of your SomeContext : DbContext class as follows:
public class DatabaseContext : DbContext
{
public DatabaseContext()
: base("SomeDB")
{
}
public DbSet<WebUser> Users { get; set; }
public DbSet<Client> Clients { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<WebUser>().HasMany(c => c.Clients)
.WithMany(p => p.WebUsers).Map(
m =>
{
m.MapLeftKey("ClientId");
m.MapRightKey("UserId");
m.ToTable("ClientUsers");
});
}
}
This assumes your classes are something like the following:
[Table("Users")]
public class WebUser
{
public virtual int Id { get; set; }
public virtual ICollection<Client> Clients { get; set; }
// more properties...
}
public class Client
{
public int Id { get; set; }
public ICollection<WebUser> WebUsers { get; set; }
// more properties
}
Finally, here's an integration test (NUnit) demonstrating the functionality working. You may need to drop your database before running it as Code First should want to update/migrate/recreate it.
[TestFixture]
public class Test
{
[Test]
public void UseDB()
{
var db = new DatabaseContext();
db.Users.Add(new WebUser { Clients = new List<Client> { new Client() } });
db.SaveChanges();
var webUser = db.Users.First();
var client = webUser.Clients.FirstOrDefault();
Assert.NotNull(client);
}
}
Edit: Link to relevant documentation for the Fluent API
Rowan's answer (adding here for reference):
Here is the information on how to configure a many-to-many table (including specifying the table name). The code you are after is something like:
modelBuilder.Entity<WebUser>()
.HasMany(u => u.Clients)
.WithMany(c => c.WebUsers)
.Map(m => m.ToTable("ClientUsers");
~Rowan
I want to retrieve an object plus its filtered/ordered collection property using EF 5. However, my current code throws an exception:
The Include path expression must refer to a navigation property
defined on the type. Use dotted paths for reference navigation
properties and the Select operator for collection navigation
properties
Here is the class of the object I want to retrieve:
public class EntryCollection
{
[Key]
public int Id { get; set; }
public ICollection<Entry> Entries { get; set; }
...
}
And here is the definition of Entry:
public class Entry
{
[Key]
public int Id { get; set; }
public DateTime Added { get; set; }
...
}
I wanted to retrieve the EntryCollection which contains only the most recent entries, so here is the code I tried:
using (var db = new MyContext())
{
return db.EntryCollections
.Include(ec => ec.Entries.OrderByDescending(e => e.Added).Take(5))
.SingleOrDefault(ec => ec.Foo == "bar');
}
Any ideas?
You cant use OrderBy inside an include.
what about the following
using (var db = new MyContext())
{
return db.EntryCollections
.Where(ec => ec.Foo == "bar")
.Select(ec=> new Something{Entries = ec.Entries.OrderByDescending(e => e.Added).Take(5) }, /*some other properties*/)
.SingleOrDefault();
}
or do it in two seperate queries
I am trying the EF5 CodeFirst and cannot get the simple setup to work ;(
I have two classes Foo and Bar where Bar represent lookup table.
public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Bar Bar { get; set; }
}
public class Bar
{
public int Id { get; set; }
public string Description { get; set; }
}
public class MyDbContext : DbContext
{
static MyDbContext()
{
Database.SetInitializer<MyDbContext>(null);
}
public MyDbContext(): base("testEF"){}
public DbSet<Foo> Foos { get; set; }
public DbSet<Bar> Bars { get; set; }
}
Now I have created a static class that serves as DataAccess Layer - in real-world application it will be on different physical tier
public static class DataAccess
{
public static Bar GetBarById(int id)
{
using (var db = new MyDbContext())
{
return db.Bars.SingleOrDefault(b => b.Id == id);
}
}
public static Foo InsertFoo(Foo foo)
{
using (var db = new MyDbContext())
{
db.Foos.Add(foo);
db.SaveChanges();
}
return foo;
}
}
I am initializing the DB with seed method:
internal sealed class Configuration : DbMigrationsConfiguration<testEF.MyDbContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(testEF.MyDbContext context)
{
context.Bars.AddOrUpdate(
new Bar { Description = "Bar_1" },
new Bar { Description = "Bar_2" }
);
}
}
This creates two records in Bars table. So far so good...
Here is my Main function
static void Main(string[] args)
{
var bar1 = DataAccess.GetBarById(1);
var foo = new Foo
{
Name = "Foo_1",
Bar = bar1
};
DataAccess.InsertFoo(foo);
}
After the app runes there is a record in the Foos table:
Id Name Bar_Id
1 Foo_1 3
Why Bar_Id is 3? The EF actually inserted new record to Bars table!
Id Description
1 Bar_1
2 Bar_2
3 Bar_1
What I am doing wrong?
UPDATE:
I have found a workaround - to attach Bar property prior to inserting the record:
public static Foo InsertFoo(Foo foo)
{
using (var db = new MyDbContext())
{
db.Bars.Attach(foo.Bar);
db.Foos.Add(foo);
db.SaveChanges();
}
return foo;
}
It is working now but this is more like a hack than a valid solution...
In real-world application the complexity of the objects could become a huge problem.
I am open to better solutions
The problem is that bar1 comes from a different data context. Your InsertFoo method implicitly adds it to the second context by building a relationship with the Foo. You want these two to share a context. So use a single context for the whole scope of the Main method.
The complexity you mention (which I agree with you) is caused by using a static class for your data access component. It forces you to separate your DBContext's across method calls. Instead of doing it that way, why not create a normal class, and build the context in the constructor.
With this, you don't need to attach foo.Bar anymore.
public class DataAccess
{
private MyDbContext _context;
public DataAccess(){
_context = new MyDbContext();
}
public Bar GetBarById(int id)
{
return _context.Bars.SingleOrDefault(b => b.Id == id);
}
public Foo InsertFoo(Foo foo)
{
_context.Foos.Add(foo);
_context.SaveChanges();
return foo;
}
}
There are many ways you can build on and enhance this. You could create an interface for MyDbContext called IDbContext and using a DI framework inject it into this class. Similarly, you could do the same for the DataAccess class and inject that into wherever it's needed.
I am using entity framework 4.1 code first.
I have a GrantApplication class:
public class GrantApplication
{
// Just some of the properties are listed
public int Id { get; set; }
public GrantApplicationState GrantApplicationState { get; set; }
}
GrantApplicationState is an enum and looks like this:
public enum GrantApplicationState
{
Applying = 1,
Submitted = 2,
cknowledged = 3
}
Just before I go and add the grant application the database I set the grant application state:
public void Insert(GrantApplication grantApplication)
{
// Set the current state to applying
grantApplication.GrantApplicationState = GrantApplicationState.Applying;
// Insert the new grant application
grantApplicationRepository.Insert(grantApplication);
}
In my database I have a GrantApplication table with a GrantApplicationStateId that links to a GrantApplicationState table.
How do I get EF to add the state id from GrantApplication.GrantApplicationState to the GrantApplicationStateId column? Is this possible? And when I retrieve the GrantApplication object then it will need to be set as well. Is this the way to do it or do I have to create another property in my GrantApplication class called GrantApplicationStateId?
You must create another property:
public class GrantApplication
{
public int Id { get; set; }
...
public int GrantApplicationStateId { get; set; }
[NotMapped] // Perhaps not need
public GrantApplicationState GrantApplicationState
{
get { return (GrantApplicationState)GrantApplicationStateId; }
set { GrantApplicationStateId = (int)value; }
}
}
EFv4.1 doesn't support enums at all - you cannot map them. This will change in EFv4.2.
Still EF not support for Enums.. it will be on EF 5.0..check my try on here
http://the--semicolon.blogspot.com/p/handling-enum-in-code-first-entity.html