June 2008 - Posts

Entity Framework: How to use Entity Splitting with different PK?

Imagine you want to create an application which manages product stocks using Northwind but which doesn’t create products.

For this, we want two Entitysets: Product and Supplier with Supplier on read only.

We also want a read only product CategoryName property.

 

How to do this with the least possible amount of work?

To begin with, we will change all Supplier properties set visibility to private. For this, we will modify xml Setter attribute.

Of course, we can do this with EDM designer properties Window (from VS 2008 SP1 Beta).

Then, we will also change the Supplier Product property Setter visibility to private and change the Products Supplier property (get + set) visibility to private.

Next, we will remove Product CategoryID property.

At this point, you should have this csdl:

<!-- CSDL content -->

<edmx:ConceptualModels>

    <Schema Namespace="NorthwindEFModel" Alias="Self" xmlns="http://schemas.microsoft.com/ado/2006/04/edm">

        <EntityContainer Name="NorthwindEFEntities">

            <EntitySet Name="Products" EntityType="NorthwindEFModel.Product" />

            <EntitySet Name="Suppliers" EntityType="NorthwindEFModel.Supplier" />

            <AssociationSet Name="FK_Products_Suppliers" Association="NorthwindEFModel.FK_Products_Suppliers">

                <End Role="Suppliers" EntitySet="Suppliers" />

                <End Role="Products" EntitySet="Products" />

            </AssociationSet>

        </EntityContainer>

        <EntityType Name="Product">

            <Key>

                <PropertyRef Name="ProductID" />

            </Key>

            <Property Name="ProductID" Type="Int32" Nullable="false" />

            <Property Name="ProductName" Type="String" Nullable="false" MaxLength="40" Unicode="true" FixedLength="false" />

            <Property Name="QuantityPerUnit" Type="String" MaxLength="20" Unicode="true" FixedLength="false" />

            <Property Name="UnitPrice" Type="Decimal" Precision="19" Scale="4" />

            <Property Name="UnitsInStock" Type="Int16" />

            <Property Name="UnitsOnOrder" Type="Int16" />

            <Property Name="ReorderLevel" Type="Int16" />

            <Property Name="Discontinued" Type="Boolean" Nullable="false" />

            <NavigationProperty Name="Supplier" Relationship="NorthwindEFModel.FK_Products_Suppliers" FromRole="Products" ToRole="Suppliers" a:SetterAccess="Private" xmlns:a="http://schemas.microsoft.com/ado/2006/04/codegeneration" />

        </EntityType>

        <EntityType Name="Supplier">

            <Key>

                <PropertyRef Name="SupplierID" />

            </Key>

            <Property Name="SupplierID" Type="Int32" Nullable="false" />

            <Property Name="CompanyName" Type="String" Nullable="false" a:SetterAccess="Private" xmlns:a="http://schemas.microsoft.com/ado/2006/04/codegeneration" />

            <Property Name="ContactName" Type="String" Nullable="true" a:SetterAccess="Private" xmlns:a="http://schemas.microsoft.com/ado/2006/04/codegeneration" />

            <Property Name="ContactTitle" Type="String" Nullable="true" a:SetterAccess="Private" xmlns:a="http://schemas.microsoft.com/ado/2006/04/codegeneration" />

            <Property Name="Address" Type="String" Nullable="true" a:SetterAccess="Private" xmlns:a="http://schemas.microsoft.com/ado/2006/04/codegeneration" />

            <Property Name="City" Type="String" Nullable="true" a:SetterAccess="Private" xmlns:a="http://schemas.microsoft.com/ado/2006/04/codegeneration" />

            <Property Name="Region" Type="String" Nullable="true" a:SetterAccess="Private" xmlns:a="http://schemas.microsoft.com/ado/2006/04/codegeneration" />

            <Property Name="PostalCode" Type="String" Nullable="true" a:SetterAccess="Private" xmlns:a="http://schemas.microsoft.com/ado/2006/04/codegeneration" />

            <Property Name="Country" Type="String" Nullable="true" a:SetterAccess="Private" xmlns:a="http://schemas.microsoft.com/ado/2006/04/codegeneration" />

            <Property Name="Phone" Type="String" Nullable="true" a:SetterAccess="Private" xmlns:a="http://schemas.microsoft.com/ado/2006/04/codegeneration" />

            <Property Name="Fax" Type="String" Nullable="true" a:SetterAccess="Private" xmlns:a="http://schemas.microsoft.com/ado/2006/04/codegeneration" />

            <Property Name="HomePage" Type="String" Nullable="true" a:SetterAccess="Private" xmlns:a="http://schemas.microsoft.com/ado/2006/04/codegeneration" />

            <NavigationProperty Name="Products" Relationship="NorthwindEFModel.FK_Products_Suppliers" FromRole="Suppliers" ToRole="Products" a:SetterAccess="Private" xmlns:a="http://schemas.microsoft.com/ado/2006/04/codegeneration" a:GetterAccess="Private" />

        </EntityType>

        <Association Name="FK_Products_Suppliers">

            <End Type="NorthwindEFModel.Supplier" Role="Suppliers" Multiplicity="0..1" />

            <End Type="NorthwindEFModel.Product" Role="Products" Multiplicity="*" />

        </Association>

    </Schema>

</edmx:ConceptualModels>

Now, we just need to add Product CategoryName property.

How to do this? We can use a SQL view instead of Products table and use stored procedures for CUD operations. In the same way, we can also use ssdl view with ssdl functions (for more information, look at my EDM article).  However, we want to do the least possible amount of work and, on this purpose, we can use a better approach.

We will define a partial ssdl view and we will use EntitySplitting to keep the Product part already generated by designer. With this approach, we won’t have to define ssdl CUD functions because we suppose Product CategoryName property is read only.

To do this, in SSDL part, we will define a new EntitySet:

<EntitySet Name="ProductCategoryName" EntityType="NorthwindEFModel.Store.ProductCategoryName">

    <DefiningQuery>

        SELECT P.ProductID, C.CategoryName

        FROM Products AS P

        INNER JOIN Categories as C ON C.CategoryID = P.CategoryID

    </DefiningQuery>

</EntitySet>

And a new EntityType (also in SSDL):

<EntityType Name="ProductCategoryName">

    <Key>

        <PropertyRef Name="ProductID" />

    </Key>

    <Property Name="ProductID" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />

    <Property Name="CategoryName" Type="nvarchar" MaxLength="15" />

</EntityType>

Now, we can back to designer and add CategoryName Product property (of type string with setter private) and do EntitySplitting on Product EntityType.

That’s all, folks! Smile You have a read only EntityType Supplier and an EntityType Product with a read only CategoryName property.

Isn’t it too easy to use Entity Framework? Smile

Clarifying "AsEnumerable: not only to use unsupported methods"

As you have probably seen with Roger comment and his blog post about this, my post "AsEnumerable: not only to use unsupported methods" wasn't very clear. So I will try to clarify it.

In my old post Entity Framework Include with Func", Niraj posted an interesting comment. In fact, he wants to do an Include with condition which doesn't return an anonymous type and Daniel Simmons proposed me a great solution but it is good only for Include with condition for an EntityCollection<T> (so children relationship end) and not for parent relationship end.

So I try to do the same for the parent relationship end.

In my request, I "want to get Northwind orders if date is greater or equal than 1998 with their property Customer only if country is France with a LINQ to Entities request". So my method will return an IEnumerable<Order>.

I agree with Roger that in most cases, it isn't a good idea to use AsEnumerable, particularly if you don't want all of the sql query results (if you use a where (except if you call a LINQ To Entities unsupported method) or a restrictive select for example) but in my sample, I think you can use AsEnumerable extension method.

Posted by Matthieu MEZIL | with no comments

AsEnumerable: not only to use unsupported methods

AsEnumerable extension method allow us to use an IEnumerable<T> instead of an IQueryable<T>. This allows, for example, to call an unsupported method by LINQ To Entities in a LINQ To Entities request.

However, this method can have another interest.

Imagine that you want to get Northwind orders if date is greater or equal than 1998 with their property Customer only if country is France with a LINQ to Entities request.

To do this, you need to load into our context the right orders and customers.

How to do this with only one LINQ request?

We can imagine that this one is good:

from oc in

    from o in context.Orders

     where o.OrderDate.HasValue && o.OrderDate.Value.Year >= 1998

     select new { Order = o, Customer = context.Customers.Where(c => c.CustomerID == o.Customers.CustomerID && c.Country == "FRANCE").FirstOrDefault() }

select oc.Order;

But no. Indeed, the SQL request only gets the Orders. So Customers aren't loaded into the context.

The generated SQL request is following:

SELECT
1 AS [C1],
[Filter1].[OrderID] AS [OrderID],
[Filter1].[EmployeeID] AS [EmployeeID],
[Filter1].[OrderDate] AS [OrderDate],
[Filter1].[RequiredDate] AS [RequiredDate],
[Filter1].[ShippedDate] AS [ShippedDate],
[Filter1].[ShipVia] AS [ShipVia],
[Filter1].[Freight] AS [Freight],
[Filter1].[ShipName] AS [ShipName],
[Filter1].[ShipAddress] AS [ShipAddress],
[Filter1].[ShipCity] AS [ShipCity],
[Filter1].[ShipRegion] AS [ShipRegion],
[Filter1].[ShipPostalCode] AS [ShipPostalCode],
[Filter1].[ShipCountry] AS [ShipCountry],
[Filter1].[CustomerID] AS [CustomerID]
FROM   (SELECT [Extent1].[OrderID] AS [OrderID], [Extent1].[CustomerID] AS [CustomerID], [Extent1].[EmployeeID] AS [EmployeeID], [Extent1].[OrderDate] AS [OrderDate], [Extent1].[RequiredDate] AS [RequiredDate], [Extent1].[ShippedDate] AS [ShippedDate], [Extent1].[ShipVia] AS [ShipVia], [Extent1].[Freight] AS [Freight], [Extent1].[ShipName] AS [ShipName], [Extent1].[ShipAddress] AS [ShipAddress], [Extent1].[ShipCity] AS [ShipCity], [Extent1].[ShipRegion] AS [ShipRegion], [Extent1].[ShipPostalCode] AS [ShipPostalCode], [Extent1].[ShipCountry] AS [ShipCountry]
 FROM [dbo].[Orders] AS [Extent1]
 WHERE ([Extent1].[OrderDate] IS NOT NULL) AND ((DATEPART (year, [Extent1].[OrderDate])) >= 1998) ) AS [Filter1]
OUTER APPLY  (SELECT TOP (1) [Extent2].[CustomerID] AS [CustomerID], [Extent2].[CompanyName] AS [CompanyName], [Extent2].[ContactName] AS [ContactName], [Extent2].[ContactTitle] AS [ContactTitle], [Extent2].[Address] AS [Address], [Extent2].[City] AS [City], [Extent2].[Region] AS [Region], [Extent2].[PostalCode] AS [PostalCode], [Extent2].[Country] AS [Country], [Extent2].[Phone] AS [Phone], [Extent2].[Fax] AS [Fax]
 FROM [dbo].[Customers] AS [Extent2]
 WHERE ([Extent2].[CustomerID] = [Filter1].[CustomerID]) AND (N'FRANCE' = [Extent2].[Country]) ) AS [Limit1]

Note that with SQL Server optimizations, this request has the same execution plan than this one :

SELECT
            1 AS C1, 
            OrderID, 
            EmployeeID, 
            OrderDate, 
            RequiredDate, 
            ShippedDate, 
            ShipVia, 
            Freight, 
            ShipName, 
            ShipAddress, 
            ShipCity, 
            ShipRegion, 
            ShipPostalCode, 
            ShipCountry, 
            CustomerID
FROM    Orders
WHERE  (OrderDate IS NOT NULL) AND ((DATEPART (year, OrderDate)) >= 1998)

Now, if we use AsEnumerable like this

from oc in

    (from o in context.Orders

    where o.OrderDate.HasValue && o.OrderDate.Value.Year >= 1998

    select new { Order = o, Customer = context.Customers.Where(c => c.CustomerID == o.Customers.CustomerID && c.Country == "FRANCE").FirstOrDefault() }

    ).AsEnumerable()

select oc.Order;

the SQL request gets the orders we want and the customers we want:

SELECT
1 AS [C1],
1 AS [C2],
[Filter1].[OrderID] AS [OrderID],
[Filter1].[EmployeeID] AS [EmployeeID],
[Filter1].[OrderDate] AS [OrderDate],
[Filter1].[RequiredDate] AS [RequiredDate],
[Filter1].[ShippedDate] AS [ShippedDate],
[Filter1].[ShipVia] AS [ShipVia],
[Filter1].[Freight] AS [Freight],
[Filter1].[ShipName] AS [ShipName],
[Filter1].[ShipAddress] AS [ShipAddress],
[Filter1].[ShipCity] AS [ShipCity],
[Filter1].[ShipRegion] AS [ShipRegion],
[Filter1].[ShipPostalCode] AS [ShipPostalCode],
[Filter1].[ShipCountry] AS [ShipCountry],
[Filter1].[CustomerID] AS [CustomerID],
[Limit1].[CustomerID] AS [CustomerID1],
[Limit1].[CompanyName] AS [CompanyName],
[Limit1].[ContactName] AS [ContactName],
[Limit1].[ContactTitle] AS [ContactTitle],
[Limit1].[Address] AS [Address],
[Limit1].[City] AS [City],
[Limit1].[Region] AS [Region],
[Limit1].[PostalCode] AS [PostalCode],
[Limit1].[Country] AS [Country],
[Limit1].[Phone] AS [Phone],
[Limit1].[Fax] AS [Fax]
FROM   (SELECT [Extent1].[OrderID] AS [OrderID], [Extent1].[CustomerID] AS [CustomerID], [Extent1].[EmployeeID] AS [EmployeeID], [Extent1].[OrderDate] AS [OrderDate], [Extent1].[RequiredDate] AS [RequiredDate], [Extent1].[ShippedDate] AS [ShippedDate], [Extent1].[ShipVia] AS [ShipVia], [Extent1].[Freight] AS [Freight], [Extent1].[ShipName] AS [ShipName], [Extent1].[ShipAddress] AS [ShipAddress], [Extent1].[ShipCity] AS [ShipCity], [Extent1].[ShipRegion] AS [ShipRegion], [Extent1].[ShipPostalCode] AS [ShipPostalCode], [Extent1].[ShipCountry] AS [ShipCountry]
 FROM [dbo].[Orders] AS [Extent1]
 WHERE ([Extent1].[OrderDate] IS NOT NULL) AND ((DATEPART (year, [Extent1].[OrderDate])) >= 1998) ) AS [Filter1]
OUTER APPLY  (SELECT TOP (1) [Extent2].[CustomerID] AS [CustomerID], [Extent2].[CompanyName] AS [CompanyName], [Extent2].[ContactName] AS [ContactName], [Extent2].[ContactTitle] AS [ContactTitle], [Extent2].[Address] AS [Address], [Extent2].[City] AS [City], [Extent2].[Region] AS [Region], [Extent2].[PostalCode] AS [PostalCode], [Extent2].[Country] AS [Country], [Extent2].[Phone] AS [Phone], [Extent2].[Fax] AS [Fax]
 FROM [dbo].[Customers] AS [Extent2]
 WHERE ([Extent2].[CustomerID] = [Filter1].[CustomerID]) AND (N'FRANCE' = [Extent2].[Country]) ) AS [Limit1]

So they are loaded into the context and we have what we expected.

How to bind Radio Buttons on an enum property?

Imagine this case:

public class MyBusinessClass

{

    public MyEnum Value { get; set; }

}

public enum MyEnum

{

    One,

    Two,

    Three,

    Four,

    Five

}

I want to generate dynamically my radio buttons and bind them on my business class.

Problem: How to bind each of my radio button on the business class?

  • I need one property per enum value to know if I must check the radio button.
    Horrible, I don't want the BLL class interface to go bad on account of my UI.
  • I need an UI proxy which encapsulate my business object and add enum value properties.
    It's better, but if my enum or, more probable, my MyBusinessClass class change, my code isn't dynamic, and so I will need to change my proxy too.
  • I will keep the proxy idea but instead of creating the same properties in BLL and in my UI proxy, I will use PropertyDescriptors.

public class MyUIBusinessProxy<BusinessType> : ICustomTypeDescriptor

{

    public MyUIBusinessProxy(BusinessType myBusinessObject)

    {

        MyBusinessObject = myBusinessObject;

    }

 

    public BusinessType MyBusinessObject { get; private set; }

 

    #region ICustomTypeDescriptor Members

    AttributeCollection ICustomTypeDescriptor.GetAttributes()

    {

        return TypeDescriptor.GetAttributes(MyBusinessObject);

    }

    string ICustomTypeDescriptor.GetClassName()

    {

        return TypeDescriptor.GetClassName(MyBusinessObject);

    }

    string ICustomTypeDescriptor.GetComponentName()

    {

        return TypeDescriptor.GetComponentName(MyBusinessObject);

    }

    TypeConverter ICustomTypeDescriptor.GetConverter()

    {

        return TypeDescriptor.GetConverter(MyBusinessObject);

    }

    EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()

    {

        return TypeDescriptor.GetDefaultEvent(MyBusinessObject);

    }

    PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()

    {

        return TypeDescriptor.GetDefaultProperty(MyBusinessObject);

    }

    object ICustomTypeDescriptor.GetEditor(Type editorBaseType)

    {

        return TypeDescriptor.GetEditor(MyBusinessObject, editorBaseType);

    }

    EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)

    {

        return TypeDescriptor.GetEvents(MyBusinessObject, attributes);

    }

    EventDescriptorCollection ICustomTypeDescriptor.GetEvents()

    {

        return TypeDescriptor.GetEvents(MyBusinessObject);

    }

    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)

    {

        return GetPropertyDescriptors(attributes);

    }

    private PropertyDescriptorCollection GetPropertyDescriptors(Attribute[] attributes)

    {

        var propsColl = TypeDescriptor.GetProperties(MyBusinessObject, attributes, true);

        var props = new List<PropertyDescriptor>();

        foreach (PropertyDescriptor prop in propsColl)

        {

            props.Add(prop);

            if (prop.PropertyType.IsEnum)

            {

                var enumValues = Enum.GetValues(prop.PropertyType);

                var enumNames = Enum.GetNames(prop.PropertyType);

                for (int index = 0; index < enumValues.Length; index++)

                    props.Add(new IsEnumValuePropertyDescriptor(MyBusinessObject, prop.Name, prop.PropertyType, attributes, enumNames[index], (int)enumValues.GetValue(index)));

            }

        }

        return new PropertyDescriptorCollection(props.ToArray());

    }

    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()

    {

        return GetPropertyDescriptors(null);

    }

    object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)

    {

        return MyBusinessObject;

    }

    #endregion ICustomTypeDescriptor Members

 

    private class IsEnumValuePropertyDescriptor : PropertyDescriptor

    {

        private BusinessType _businessObject;

        private string _propertyName;

        private Type _propertyType;

        private int _value;

 

        public IsEnumValuePropertyDescriptor(BusinessType businessObject, string propertyName, Type propertyType, Attribute[] attr, string valueName, int value)

            : base (propertyName + "Is" + valueName, attr)

        {

            _businessObject = businessObject;

            _propertyName = propertyName;

            _propertyType = propertyType;

            _value = value;

        }

 

        public override bool CanResetValue(object component)

        {

            return false;

        }

        public override Type ComponentType

        {

            get { return typeof(BusinessType); }

        }

        public override object GetValue(object component)

        {

            return (int)(typeof(BusinessType).GetProperty(_propertyName).GetValue(_businessObject, null)) == _value;

        }

        public override bool IsReadOnly

        {

            get { return false; }

        }

        public override Type PropertyType

        {

            get { return typeof(bool); }

        }

        public override void ResetValue(object component)

        {

        }

        public override void SetValue(object component, object value)

        {

            if ((bool)value)

                typeof(BusinessType).GetProperty(_propertyName).SetValue(_businessObject, _value, null);

        }

        public override bool ShouldSerializeValue(object component)

        {

            return false;

        }

    }

}

And now, I can easily do this:

foreach (var enumName in Enum.GetNames(typeof(MyEnum)))

{

    var rb = new RadioButton();

    myFlowLayoutPanel.Controls.Add(rb);

    rb.Text = enumName;

    rb.DataBindings.Add("Checked", myUIBusinessProxy, "ValueIs" + enumName);

}

Posted by Matthieu MEZIL | 1 comment(s)
Filed under: ,

Managed Extensibility Framework CTP1

Krzysztof Cwalina announced first public Managed Extensibility Framework (MEF) CTP.

What's this? MEF is a framework which simplify add-in/plug-in in your applications.

Enjoy :-)

Posted by Matthieu MEZIL | with no comments
Filed under: ,

Entity Framework Include with Func next

I defined an Include with Func.

Cool. But what about Include("Products.Order_Details")?

Indeed my Include can only take one relationship level.

So I change my code like this:

public static class ObjectQueryExtension

{

    public static ObjectQuery<T> Include<T>(this ObjectQuery<T> mainQuery, Expression<Func<T, object>> subSelector)

    {

        return mainQuery.Include(FuncToString(subSelector.Body));

    }

    private static string FuncToString(Expression selector)

    {

        switch (selector.NodeType)

        {

            case ExpressionType.MemberAccess:

                return ((selector as MemberExpression).Member as Reflection.PropertyInfo).Name;

            case ExpressionType.Call:

                var method = selector as MethodCallExpression;

                return FuncToString(method.Arguments[0]) + "." + FuncToString(method.Arguments[1]);

            case ExpressionType.Quote:

                return FuncToString(((selector as UnaryExpression).Operand as LambdaExpression).Body);

        }

        throw new InvalidOperationException();

    }

    public static K Include<T, K>(this EntityCollection<T> mainQuery, Expression<Func<T, object>> subSelector)

        where T : EntityObject, IEntityWithRelationships

        where K : class

    {

        return null;

    }

    public static K Include<T, K>(this T mainQuery, Expression<Func<T, object>> subSelector)

        where T : EntityObject

        where K : class

    {

        return null;

    }

}

and I can now do this:

context.Categories.Include(ca => ca.Products.Include<Products, Order_Details>(p => p.Order_Details).Include<Order_Details, Orders>(od => od.Orders))

Isn't it cool?

Entity Framework Include with Func

In Northwind DB, if you want to load categories with products, you will use Include method:

context.Categories.Include("Products")

But what I really find bad is the fact that Products is a string. When we use LINQ, we can't understand it.

So I define an extension method:

public static class ObjectQueryExtension

{

    public static ObjectQuery<T> Include<T>(this ObjectQuery<T> mainQuery, Expression<Func<T, object>> subSelector)

    {

        return mainQuery.Include(((subSelector.Body as MemberExpression).Member as Reflection.PropertyInfo).Name);

    }

}

and I can now doing this:

context.Categories.Include(c => c.Products)

which is I think really better. Isn't it?

 

EF relationships and the difficulties to add entity

I had an interesting question.

This code:

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    var p = new Products { ProductName = "test", Categories = c };

    Console.WriteLine(c.Products.Count);

}

 

attach p to the context. Why?

You probably know that Entity Framework doesn't support Lazy Loading. So when you do p.Categories, you will have the category in the ObjectContext ObjectStateManager. If it isn’t loaded, you will have null.

So, when you affect a category (existing in the context) to a new product, the product will also be attached.

If you don't want this, you must use EntityReference:

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    var p = new Products { ProductName = "test" };

    p.CategoriesReference.EntityKey = c.EntityKey;

}

Attach to context implies that following code:

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    var p = new Products { ProductName = "test", Categories = c };

    Console.WriteLine(c.Products.Count);

    p.Categories = context.Categories.First(categ => categ.CategoryID == 2);

    Console.WriteLine(c.Products.Count);

}

 

 

will return 1 then 0.

 

With following code:

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    var p = new Products { ProductName = "test", Categories = c };

    context.SaveChanges();

}

 

it may seem strange that you will have a new DB record without calling Add on the context (neither context.AddToProduct, nor context.AddObject). In fact p.Categories = c attaches p to the context AND changes p EntityState from Detached to Added. It's why SaveChanges adds a record in DB.

If you remove c ChangeTracker before linking it to p:

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    (c as IEntityWithChangeTracker).SetChangeTracker(null);

    var p = new Products { ProductName = "test"};

    p.Categories = c;

    context.SaveChanges();

}

 

When you affect null to c ChangeTracker, this means that you will be able to attach c to another context but until you do it c EntityChangeTracker keeps the old one:
 

private IEntityChangeTracker EntityChangeTracker

{

    get

    {

        if (this._entityChangeTracker == null)

        {

            this._entityChangeTracker = s_detachedEntityChangeTracker;

        }

        return this._entityChangeTracker;

    }

    set

    {

        this._entityChangeTracker = value;

    }

}

 

So in this case, you will have a new DB record.

Now, what does happen in this case?

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    (c as IEntityWithChangeTracker).SetChangeTracker(null);

    var p = new Products { ProductName = "test"};

    using (var context2 = new NorthwindEntities())

    {

        context2.Attach(c);

        p.Categories = c;

    }

    var c2 = context.Categories.First(categ => categ.CategoryID == 1);

    Console.WriteLine(c2.Products.Count);

    Console.WriteLine(c.Products.Count);

    context.SaveChanges();

}

 

p isn't saved on DB because it is linked to context2 but, Console shows 1 and 1 because with cache use, second context.Categories.First(categ => categ.CategoryID == 1) returns c.

So following code:

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    (c as IEntityWithChangeTracker).SetChangeTracker(null);

    var p = new Products { ProductName = "test"};

    using (var context2 = new NorthwindEntities())

    {

        context2.Attach(c);

    }

    var c2 = context.Categories.First(categ => categ.CategoryID == 1);

    p.Categories = c2;

    Console.WriteLine(c2.Products.Count);

    Console.WriteLine(c.Products.Count);

    context.SaveChanges();

}

 

doesn't add product DB record.
 
It's strange. So to add my product DB record in this scenario, I need to do this:
 

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    (c as IEntityWithChangeTracker).SetChangeTracker(null);

    var p = new Products { ProductName = "test" };

    using (var context2 = new NorthwindEntities())

    {

        context2.Attach(c);

        var c2 = context.Categories.First(categ => categ.CategoryID == 1); // useless, we should use directly c in this case

        p.Categories = c2;

        context2.SaveChanges();

    }

}

Now, what does happen with Detach?

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    context.Detach(c);

    var p = new Products { ProductName = "test"};

    p.Categories = c;

    Console.WriteLine(c.Products.Count);

    context.SaveChanges();

}

 

Of course, p won't be saved in DB but console shows 1. Indeed, when you detach an entity, this one is attached to a DetachedEntityChangeTracker which will include p.

Last point. This code:

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    context.Detach(c);

    var p = new Products { ProductName = "test"};

    p.Categories = c;

    context.AddToProducts(p);

}

 

fails with an InvalidOperationException "The object cannot be added to the ObjectStateManager because it already has an EntityKey. Use ObjectContext.Attach to attach an object that has an existing key." on the Add.

But not this one:

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    var p = new Products { ProductName = "test"};

    p.Categories = c;

    context.AddToProducts(p);

}

 

Why?

There are two points. When you detach an entity, context ObjectStateManager will keep cache information. Then, when you do Add on the context, you will add all the graph. So, you add a new product AND our category which will generate a conflict with ObjectStateManager cache (=> exception). In the second case, nothing is done on the Add. Indeed, if the same entity is already in the context with an Added EntityState (it's the case), Add will do nothing and the product graph Add won't add the category because this one is already attached to the context.

Moreover, note that following code:

using (var context = new NorthwindEntities())

{

    var p = new Products { ProductName = "test"};

    context.AddToProducts(p);

    context.AddToProducts(p);

    context.SaveChanges();

}

 

will generate only one DB record.

Finally, this code:

using (var context = new NorthwindEntities())

{

    var c = new Categories { CategoryName = "test" };

    var p = new Products { ProductName = "test" };

    p.Categories = c;

    context.AddToProducts(p);

}

 

will add the product and the category to the context without problem (of course).

Following code:

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    context.Detach(c);

    using (var context2 = new NorthwindEntities())

    {

        var p = new Products { ProductName = "test" };

        p.Categories = c;

        context2.AddToProducts(p);

    }

}

 

will fail for the same raison than before but not this one:
 

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    context.Detach(c);

    using (var context2 = new NorthwindEntities())

    {

        context2.AttachTo("Categories", c);

        var p = new Products { ProductName = "test" };

        p.Categories = c;

        context2.AddToProducts(p);

    }

}

 

When you attach c, its EntityState pass from Detached to Unchanged. Note that in this case AddToProducts is useless because category is attached to the context when you do context2.AttachTo("Categories", c) and so you are on the same case than before:
 

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    var p = new Products { ProductName = "test" };

    p.Categories = c;

}

 

But in this case:
 

using (var context = new NorthwindEntities())

{

    var c = context.Categories.First(categ => categ.CategoryID == 1);

    context.Detach(c);

    using (var context2 = new NorthwindEntities())

    {

        var p = new Products { ProductName = "test" };

        p.Categories = c;

        context2.AttachTo("Categories", c);

        context2.AddToProducts(p);

    }

}

 

code fails with an InvalidOperationException "An object with the same key already exists in the ObjectStateManager. The existing object is in the Unchanged state. An object can only be added to the ObjectStateManager again if it is in the added state." because when you attach the category, you attach all the graph, so you attach p with Unchanged EntitySet and when you want to add it, you get the exception.

Reflector IL to C# Bug

I was watching Entity Framework code when I found this code:

switch (this.<>1__state)
{
    case 3:
    case 4:
        break;

    default:
        return;
        try
        {
        }
        finally
        {
            this.d__0.<>m__Finally6();" href="http://www.aisto.com/roeder/dotnet/Default.aspx?Target=code://System.Data.Entity:3.5.0.0:b77a5c561934e089/System.Data.EntityModel.SchemaObjectModel.EntityContainerAssociationSet.d__0/<>m__Finally6()"><>m__Finally6();
        }
        break;
}

Very strange. It was the same with VB.Net. So I watched IL and I undestood that Reflector has a bug.

Indeed the IL was next:

L_0023: ldfld int32 System.Data.EntityModel.SchemaObjectModel.EntityContainerAssociationSet/<get_Ends>d__0::<>1__state
L_0028: stloc.1
L_0029: ldloc.1
L_002a: ldc.i4.3
L_002b: sub
L_002c: switch (L_003a, L_003a)
L_0039: ret
L_003a: leave.s L_0043
L_003c: ldarg.0
L_003d: call instance void System.Data.EntityModel.SchemaObjectModel.EntityContainerAssociationSet/<get_Ends>d__0::<>m__Finally6()
L_0042: endfinally
L_0043: ret
.try L_003a to L_003c finally handler L_003c to L_0043

So C# code should be:

switch (this.<>1__state)
{
    case 3:
    case 4:
        try
        {
        }
        finally
        {
            this.d__0.<>m__Finally6();" href="http://www.aisto.com/roeder/dotnet/Default.aspx?Target=code://System.Data.Entity:3.5.0.0:b77a5c561934e089/System.Data.EntityModel.SchemaObjectModel.EntityContainerAssociationSet.d__0/<>m__Finally6()"><>m__Finally6();
        }
        break;
    default:
        return;
}

Posted by Matthieu MEZIL | with no comments
Filed under: , , , ,

Reflector Bug with generics

For this code:

class A<T>

{

    public A()

    {

        var b = new B();

    }

    class B : A<T>

    {

    }

}

Reflector generates this:

class A<T>

{

    public A()

    {

        var b = new B<T>();

    }

    class B : A<T>

    {

    }

}

which isn't correct.

Posted by Matthieu MEZIL | 2 comment(s)
Filed under: ,

ApplyPropertyChanges

I see that too few developpers really know how to use ApplyPropertyChanges ObjectContext's method.

So I will enumerate some importants points.

ApplyPropertyChanges is used when you have an entity detached of its context and you would like to save the modifications on it.

You have two choices.

  • You use optimistic mode. You keep from the DB the entity with the same EntityKey (using TryGetObjectByKey for example), to be sure that the entity is loaded on the context. Then, you call ApplyPropertyChanges with modified property.
  • You do not use optimistic mode. Remember that the optimitic mode is done per property. You need to use ConcurrencyMode=fixed on EDM for each property to verify before SaveChanges. In this case, you need to keep a copy of your original entity, attach the copy to the context and then call ApplyPropertyChanges.

In this last case, my EntityCloner (even simple), can be very useful.

An other important point: ApplyPropertyChanges method doesn't update relationships changes.

When I will have a little more time, I will try to do, in the same way that my CloneWithGraph, an ApplyPropertyChangesWithGraph method.

Who blog about Entity Framework?

I had recently this questions:

"Matthieu, can you tell me some blogs about Entity Framework?"

There is a a very useful page about it.