views:

173

answers:

3

I follow by entity framework example :

http://msdn.microsoft.com/en-us/library/bb399182.aspx

and I have problem with Identity Columns.

Here is part of code of creating database:

CREATE TABLE [dbo].[Person](
    [PersonID] [int] IDENTITY(1,1) NOT NULL,
    [LastName] [nvarchar](50) NOT NULL,
    [FirstName] [nvarchar](50) NOT NULL,
    [HireDate] [datetime] NULL,
    [EnrollmentDate] [datetime] NULL,
 CONSTRAINT [PK_School.Student] PRIMARY KEY CLUSTERED 
(
    [PersonID] ASC
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
GO

In VS 2010 I build .edmx and at model I see that Person StoreGeneratedPattern is set to Identity.

But when I want to create Person, by : alt text

Why I must put id, if this column is autoincrement?

EDITŁ

I thought that I found the way to resolve my problem by:

var schoolContext = new SchoolEntities();

            schoolContext.AddToPeople(new Person() { LastName = "Gates", FirstName = "Bil" });

            schoolContext.SaveChanges();

because it added Bill to persons, but...because PersonID is not nullable, and it inserted him with id 0. When I tried add another person the same way of course I get error about primary key :)

So still with nothing...

Any ideas ?

+2  A: 

I cannot tell you why the EF team chose to do it this way - my only guess would be that the code generation that create the CreatePerson method doesn't check to see whether or not the ID is an autoincrement and just creates a method that will work in any circumstance - whether the ID is autoincrement or not.

If this really bothers you, you can also benefit from the fact that the generated entity class Person is defined as a partial class, so you can extend it easily. Create a new class file called e.g. PersonEx.cs and extend that partial class:

public partial class Person 
{
    public static Person CreatePerson(string lastName, string firstName)
    {
        return CreatePerson(-1, lastName, firstName);
    }
}

Now, you can easily create your Person entities without specifying an ID, and add them to your data:

 using(SchoolEntities context = new SchoolEntities())
 {
     Person newPerson = Person.CreatePerson("Gates", "Bill");
     context.AddToPeople(newPerson);

     context.SaveChanges();

     int newID = newPerson.PersonID;
 }

It's not a perfect solution - but it should work just fine (at least it does for me).

marc_s
Thank you Marc ! It's next time you save me a lot of time
+1  A: 

IMO the Id is needed even if it is generated. Suppose that you are using Foreign Key association (different behavior than independent association). It means that related child entities are using primary key of the parent entity to build relation. Now suppose that you are adding multiple parent entities with related entities in single unit of work. You have to specify unique (temporary) Id for each parent entity otherwise you will never configure your object graph. So the code generator probably makes this as default.

Edit:

I'm surprised that I have been downvoted for answer based on correct facts. So let me clarify my answer:

There are two types of relations available in EF 4.0. Independent association and Foreign key association. The diference is that the later one adds foreign key properties to entities. This allow you to work with relations in the same way as in database - simply by setting keys. You can read about these diferences in MSDN.

Now lets assume simple example. I have simple EDMX model with MyContext. Model consists of two entities Order and OrderLine. When I added Orders and OrderLines tables to the model I thicked Include foreign keys columns in model so I'm using Foreign keys associations instead of independent associations.

Order has store generated Id as a key and CustomerName as a property. Order line has store generated Id as a key, ProductTitle as property and OrderId as foreign key. I want to add two orders in single unit of work:

using (var context = new MyContext())
{
  var ox = Order.CreateOrder(0, "CustomerX");
  var oy = Order.CreateOrder(0, "CustomerY");

  // Building relationship in the same way as in database
  var olx = OrderLine.CreateOrderLine(0, ox.Id, "ProductX");
  var oly = OrderLine.CreateOrderLine(0, oy.Id, "ProductY");

  context.Orders.AddObject(ox);
  context.Orders.AddObject(oy);
  context.OrderLines.AddObject(olx);
  context.OrderLines.AddObject(oly);
  context.SaveChanges(); // UpdateException: Unable determine principal end of Model.FK_OrderLine_Order relationship. Multiple added entities have the same primary key.
}

The mistake I did in my code is setting Id of both new orders to 0. Even if this is temporary Id it still has to be unique if you want to use it for foreign keys. You can probably ask at the moment why the context doesn't handle Id by itself? Simple because it can't. Context knows that Id is temporary and will be regenerated in store but context doesn't know details about store configuration. When you set up Identity in database you also set up seed and increment. Those two values are not known to the context so the context is not able to derive valid unique temporary Id which is not already used by the store. Suppose that context wrongly create some temporary Id and after that you load entity which already uses this Id = problem.

If i simply update my code to use unique temporary Id (I know how the store is configured) it will work. That is IMO one reason why I need to provide temporary Id to Create methods.

Ladislav Mrnka
I don't think there will be a problem if you have several parent objects with `ID=-1` with child objects. It will just insert parent for parent, and use `@@IDENTITY` to link the childs. The `Id` property isn't used for creating the object graph.
Sander Rijken
What you described works for independent association. I described foreign key association.
Ladislav Mrnka
+1  A: 

You should be able to customize the t4 template that generates your classes to remove the property from the factory method. See Danny Simmons' blog post for info on how to create the t4. I haven't tested the resulting factory method, but I can't see why it wouldn't work.

Then modify this method:

bool IncludePropertyInFactoryMethod(StructuralType factoryType, EdmProperty edmProperty)
{
    if (edmProperty.Nullable)
    {
        return false;
    }

    if (edmProperty.DefaultValue != null)
    {
        return false;
    }

    if ((Accessibility.ForReadOnlyProperty(edmProperty) != "public" && Accessibility.ForWriteOnlyProperty(edmProperty) != "public") ||
        (factoryType != edmProperty.DeclaringType && Accessibility.ForWriteOnlyProperty(edmProperty) == "private")
       )
    {
        //  There is no public part to the property.
        return false;
    }

    /*********
     * This was added:
     */

    var identity = edmProperty.TypeUsage.Facets
        .Where(f => f.Name == "StoreGeneratedPattern").FirstOrDefault();

    if (identity != null && identity.Value.ToString() == "Identity")
    {
        // property is "Identity" generated, so skip from the factory method.
        return false;
    }

    /*********
     * end of the custom code
     */

    return true;
}
Sander Rijken