Last few weeks I have been learning entity Framework, had exposure to ADO.Net and some LINQ syntax. I am basically finding so much setup and wiring to do before you can actually get into the developing the stuffs. I am especially having hard time in Code First approach, have also spent sometime in passionate blogs of Julie Larnam. Any suggestions on books, articles or blogs on entity framework to expedite my learning and deeper understanding would be appreciated.
Thanks
Alaxi
Initialization
Step one, setup your models
public class Person
{
public Int32 Id { get; set; }
public String Name { get; set; }
public String Email { get; set; }
// one-to-many relationship to Pet (EF picks up on this automatically)
public ICollection<Pet> Pets { get; set; }
}
public class Pet
{
public Int32 Id { get; set; }
public String Name { get; set; }
public String Breed { get; set; }
// foreign key back to person (EF picks up on this automatically)
public Person Owner { get; set; }
}
Step two, create a context using your models
public MyContext : DbContext
{
public DbSet<Person> Persons { get; set; }
public DbSet<Pet> Pets { get; set; }
}
Step three, create a connectionString
protip: The name of the connection string should match that of your context.
<connectionStrings>
<add name="MyContext"
providerName="System.Data.SqlClient"
connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=MyContext;IntegratedSecurity=True;" />
</connectionStrings>
Run through
Basic
// automatically find the connection string matching the context name,
// as well as performs a check to see if:
// 1. The database exists
// 2. The schema is up-to-date
MyContext context = new MyContext();
context.Persons.Add(new Person {
Name = "Brad Christie",
Email = "bchristie#contoso.com"
});
context.SaveChanges();
Intermediate
You can also change how the data is generated using Initializers. For example, if you wanted to pre-populate the database with information you can do the following:
public class MyContextInitializer
// this could be `DropCreateDatabaseAlways<TContext> or any other
// preexsting initializer:
: DropCreateDatabaseIfModelChanges<MyContext>
// you can also create your own explicitly if you implement the
// following interface, but that's a bit much starting out.
//: IDatabaseInitializer<MyContext>
{
protected override void Seed(MyContext context)
{
new List<Person> {
new Person {
Name = "Brad Christie",
Email = "bchristie#contoso.com",
Pets = new HashSet<Pet> {
new Pet {
Name = "Spot",
Breed = "Dalmation"
}
}
}, new Person {
Name = "Alaxi04",
Email = "Alaxi04#contoso.com",
Pets = new HashSet<Pet> {
new Pet {
Name = "Petey",
Breed = "Parrot"
}
}
}
}.ForEach(p => context.Persons.Add(p));
base.Seed(context);
}
}
In practice:
// call this somewhere early on in the application (but only once!)
Database.SetInitializer<MyContext>(new MyContextInitializer());
// This can also be configured through the `web.config`:
<entityFramework>
<contexts>
<context type="MyNamespace.MyContext, MyAssembly">
<databaseInitializer type="MyNamespace.MyContextInitializer, MyAssembly" />
</context>
</contexts>
</entityFramework>
/* ***** */
// then use the context as normal:
MyContext context = new MyContext();
var petOwners = context.Persons.AsEnumerable();
Related
I'm trying to fetch (in disconnected way) an entity with its all related entities and then trying to update the entity. But I'm getting the following error:
Attaching an entity of type 'Feature' failed because another entity of the same type already has the same primary key value.
public class Person
{
public int PersonId { get; set; }
public string Personname { get; set }
public ICollection Addresses { get; set; }
}
public class Address
{
public int AddressId { get; set; }
public int PersonId { get; set; }
public string Line1 { get; set; }
public string City { get; set; }
public string State { get; set; }
public Person Person { get; set; }
public ICollection<Feature> Features { get; set; }
}
// Many to Many: Represented in database as AddressFeature (e.g Air Conditioning, Central Heating; User could select multiple features of a single address)
public class Feature
{
public int FeatureId { get; set; }
public string Featurename { get; set; }
public ICollection<Address> Addresses { get; set; } // Many-To-Many with Addresses
}
public Person GetCandidate(int id)
{
using (MyDbContext dbContext = new MyDbContext())
{
var person = dbContext.People.AsNoTracking().Where(x => x.PersonId == id);
person = person.Include(prop => prop.Addresses.Select(x => x.Country)).Include(prop => prop.Addresses.Select(x => x.Features));
return person.FirstOrDefault();
}
}
public void UpdateCandidate(Person newPerson)
{
Person existingPerson = GetPerson(person.Id); // Loading the existing candidate from database with ASNOTRACKING
dbContext.People.Attach(existingPerson); // This line is giving error
.....
.....
.....
}
Error:
Additional information: Attaching an entity of type 'Feature' failed because another entity of the same type already has the same primary key value.
It seems like (I may be wrong) GetCandidate is assigning every Feature within Person.Addresses a new instance. So, how could I modify the GetCandidate to make sure that the same instance (for same values) is bing assisgned to Person.Addresses --> Features.
Kindly suggest.
It seems like (I may be wrong) GetCandidate is assigning every Feature within Person.Addresses a new instance. So, how could I modify the GetCandidate to make sure that the same instance (for same values) is bing assisgned to Person.Addresses --> Features.
Since you are using a short lived DbContext for retrieving the data, all you need is to remove AsNoTracking(), thus allowing EF to use the context cache and consolidate the Feature entities. EF tracking serves different purposes. One is to allow consolidating the entity instances with the same PK which you are interested in this case, and the second is to detect the modifications in case you modify the entities and call SaveChanges(), which apparently you are not interested when using the context simply to retrieve the data. When you disable the tracking for a query, EF cannot use the cache, thus generates separate object instances.
What you really not want is to let EF create proxies which hold reference to the context used to obtain them and will cause issues when trying to attach to another context. I don't see virtual navigation properties in your models, so most likely EF will not create proxies, but in order to be absolutely sure, I would turn ProxyCreationEnabled off:
public Person GetCandidate(int id)
{
using (MyDbContext dbContext = new MyDbContext())
{
dbContext.Configuration.ProxyCreationEnabled = false;
var person = dbContext.People.Where(x => x.PersonId == id);
person = person.Include(prop => prop.Addresses.Select(x => x.Country)).Include(prop => prop.Addresses.Select(x => x.Features));
return person.FirstOrDefault();
}
}
I am Using Entity Framework 6.1.1
I have a class in one project, which include FBuyShopContext, another for models, and one Asp project with MVC 5
In my first project I have following
public class FBuyShopContext : DbContext
{
public FBuyShopContext()
: base("name=FBuyShopContext")
{
}
public virtual DbSet<Product> Admins { get; set; }
}
}
And a Product Model in a other Library project
public class Product
{
public Product()
{}
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
my connection string is following
<connectionStrings>
<add name="FBuyShopContext" connectionString="data source=tschikovani\SQLEXPRESS;initial catalog=OnlineShop;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"" providerName="System.Data.EntityClient" />
</connectionStrings>
and when I want to add a new product into database in my controller
FBuyShopContext db = new FBuyShopContext();
Product new = new Product ();
new.Name = "Iphone";
db.Products.Add(new);
db.SaveChanges();
I have a following exception
An exception of type 'System.ArgumentException' occurred in EntityFramework.dll
keyword not supported: 'data source'.
Why this happened? connection string is fully right
I can see three things going on here:
1) DbContext's contructor should take the name of the connection string, therefore change it to just FBuyShopContext and not name=FBuyShopContext.
public class FBuyShopContext : DbContext
{
public FbuyShopContext() : base("FBuyShopContext") {}
}
2) Your connection string ends with a ", which is an invalid character. You should remove it.
3) You should specify your primary key on the model, either using the fluent API, or by data annotations:
public class Product {
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
I'm pretty sure the error you are getting is caused by 2), but 1) and 3) would have caused you problems later.
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've successfully mapped an Entity Framework Code-First data model with an existing Sql Server Compact database by a declarative approach using app.config but it would be great if I could build such connection programmatically with perhaps the help of the EntityConnectionStringBuilder (System.Data.EntityClient) class. Unfortunately this approach is not working as the [DbContext].Connection.ConnectionString is not accepting most of its properties.
Here's the actual working code with the faulty one commented out:
Book.cs
public class Book
{
[Key]
public string Isbn { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public string Publisher { get; set; }
public DateTime Published { get; set; }
public int Pages { get; set; }
public bool InStock { get; set; }
public string Description { get; set; }
}
Catalog.cs
public class Catalog : DbContext
{
public DbSet<Book> Books { get; set; }
}
app.config
<?xml version="1.0"?>
<configuration>
<connectionStrings>
<add
name="Catalog"
providerName="System.Data.SqlServerCE.4.0"
connectionString="Data Source=res/Catalog.sdf"
/>
</connectionStrings>
</configuration>
Main()
static void Main()
{
// var res = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "res");
// var str = new EntityConnectionStringBuilder();
// str.Name = "Catalog";
// str.Provider = "System.Data.SqlServerCe.4.0";
// str.ProviderConnectionString = String.Format("Data Source {0}", Path.Combine(res, "Catalog.sdf"));
try
{
using (var catalog = new Catalog())
{
// catalog.Database.Connection.ConnectionString = str.ConnectionString;
// remaining code not relevant - skipped
I've tried using other builder classes such as SqlCeConnectionStringBuilder (System.Data.SqlServerCe), SqlConnectionStringBuilder (System.Data.SqlClient) and even DbConnectionStringBuilder (System.Data.Common) but apparently none of them seem to match what [DbContext].Connection.ConnectionString is expecting.
Should I conclude there is no programmatic way to achieve this?
Any advice will be surely appreciated. Thanks much in advance for your contributions.
If you are using SQL Server Compact 4.0 and DbContext with code first you cannot use EntityConnectionStringBuilder - it builds connection string for EF with EDMX file. You need SqlCeConnectionStringBuilder from System.Data.SqlServerCe assembly with version 4.0.0.0!
You should also pass the connection string to the context instance through the constructor - DbContext has constructor accepting name of the connection string (from configuration) or connection string itself.
Open a DbConnection and pass it to the DbContext constructor or use a DefaultConnectionFactory:
Database.DefaultConnectionFactory
= new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");
using (var catalog = new Catalog("Catalog.sdf"))
{
}
SqlCeConnectionFactory is a ready-to-use connection factory provided by Microsoft, but you can also implement your own factory.
I have a parent object book, and a property of that object is publisher. Everytime I ad a book, it is adding a new publisher, even if the publisher already exists. Can someone tell me how to add the book and instead of adding the publisher again, just reference an existing one? The code i am using is below... Thanks in advance!
public class Book
{
public int BookID { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public DateTime CreateDate { get; set; }
public virtual Publisher Publisher { get; set; }
}
public class Publisher
{
public int PublisherID { get; set; }
public string Address { get; set; }
}
public class SqlCEDataStore : DbContext
{
public DbSet<Book> Books { get; set; }
public DbSet<Publishers> Publishers { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.IncludeMetadataInDatabase = false;
}
}
public class TimeSinkRepository : IRepository<Book>
{
private static SqlCEDataStore context = new SqlCEDataStore();
public int Add(Book entity)
{
context.Books.Add(entity);
return context.SaveChanges();
}
}
var book = new Book()
{
Title = "New Title",
Description = "New Description",
CreateDate = DateTime.Now,
Publisher = new Publisher() { PublisherID = 1 }
};
var repository = new BookRepository();
var result = repository.Add(book);
The problem is in the line:
Publisher = new Publisher() { PublisherID = 1 }
Object context doesn't know that this is existing publisher. It is newly created entity so Object context will perform insert operation. You have to say object context that the publisher object is not newly created. One way to do that is modification of your Add method:
public int Add(Book entity)
{
context.Books.Add(entity);
// 0 means new one, other values mean existing one
if (entity.Publisher.PublisherID > 0)
{
context.ObjectStateManager.ChangeObjectState(entity.Publisher, EntityState.Unchanged);
}
context.SaveChanges();
}
It you can solve this by making sure the Publisher is attached to Publishers context before adding the Book entity (this way it knows it's a Publisher from the dbcontext and not a new one that it needs to add (again))
context.Publishers.Attach(book.Publisher); // This is only possible if the Publisher is not new
context.Books.Add(book);
the problem is in this line
Publisher = new Publisher() { PublisherID = 1 }
You should do a fetch method so something like this
- Get the Publisher you want from the context (eg where id = 1)
- Set the returned object as the publisher for your new book object
- The context should sort the rest out for you. when you save the book. (no need to mess with the object state manager)
Good luck, if you cant get this working put up some code of it and i will help you though it.