LINQ to SQL - 使用抽象基类时的映射异常

发布于 2024-07-25 05:45:50 字数 2865 浏览 6 评论 0原文

问题:我想在多个程序集之间共享代码。 此共享代码需要与 LINQ to SQL 映射类一起使用。

我在此处遇到了同样的问题,但我也找到了一项工作-围绕这一点我觉得很麻烦(我不会说“错误”)。

以下所有代码都可以在此解决方案中下载。给

定此表:

create table Users
(
      Id int identity(1,1) not null constraint PK_Users primary key
    , Name nvarchar(40) not null
    , Email nvarchar(100) not null
)

和此 DBML 映射:

<Table Name="dbo.Users" Member="Users">
  <Type Name="User">
    <Column Name="Id" Modifier="Override" Type="System.Int32" DbType="Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true" CanBeNull="false" />
    <Column Name="Name" Modifier="Override" Type="System.String" DbType="NVarChar(40) NOT NULL" CanBeNull="false" />
    <Column Name="Email" Modifier="Override" Type="System.String" DbType="NVarChar(100) NOT NULL" CanBeNull="false" />
  </Type>
</Table>

我在一个程序集“共享”中创建了以下基类:

namespace TestLinq2Sql.Shared
{
    public abstract class UserBase
    {
        public abstract int Id { get; set; }
        public abstract string Name { get; set; }
        public abstract string Email { get; set; }
    }

    public abstract class UserBase<TUser> : UserBase where TUser : UserBase
    {
        public static TUser FindByName_Broken(DataContext db, string name)
        {
            return db.GetTable<TUser>().FirstOrDefault(u => u.Name == name);
        }

        public static TUser FindByName_Works(DataContext db, string name)
        {
            return db.GetTable<TUser>().FirstOrDefault(u => u.Name == name && 1 == 1);
        }

        public static TUser FindByNameEmail_Works(DataContext db, string name, string email)
        {
            return db.GetTable<TUser>().FirstOrDefault(u => u.Name == name || u.Email == email);
        }
    }
}

这些类在另一个程序集“主”中引用,如下所示:

namespace TestLinq2Sql
{
    partial class User : TestLinq2Sql.Shared.UserBase<User>
    {

    }
}

DBML 文件位于“主”组件也是如此。

调用User.FindByName_Broken(db, "test")时抛出异常:

System.InvalidOperationException:类成员 UserBase.Name 未映射。

但是,其他两个基本静态方法可以工作。

此外,通过调用 User.FindByName_Works(db, "test") 生成的 SQL 正是我们在损坏的调用中所希望的:

SELECT TOP (1) [t0].[Id], [t0].[Name], [t0].[Email]
FROM [dbo].[Users] AS [t0]
WHERE [t0].[Name] = @p0
-- @p0: Input NVarChar (Size = 4; Prec = 0; Scale = 0) [test]

虽然我愿意使用这个 1 == 1< /code> “hack”对于单个谓词查询,是否有更好的方法在基本/共享/核心程序集中共享 LINQ to SQL 感知代码?

Problem: I would like to share code between multiple assemblies. This shared code will need to work with LINQ to SQL-mapped classes.

I've encountered the same issue found here, but I've also found a work-around that I find troubling (I'm not going so far as to say "bug").

All the following code can be downloaded in this solution.

Given this table:

create table Users
(
      Id int identity(1,1) not null constraint PK_Users primary key
    , Name nvarchar(40) not null
    , Email nvarchar(100) not null
)

and this DBML mapping:

<Table Name="dbo.Users" Member="Users">
  <Type Name="User">
    <Column Name="Id" Modifier="Override" Type="System.Int32" DbType="Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true" CanBeNull="false" />
    <Column Name="Name" Modifier="Override" Type="System.String" DbType="NVarChar(40) NOT NULL" CanBeNull="false" />
    <Column Name="Email" Modifier="Override" Type="System.String" DbType="NVarChar(100) NOT NULL" CanBeNull="false" />
  </Type>
</Table>

I've created the following base classes in one assembly "Shared":

namespace TestLinq2Sql.Shared
{
    public abstract class UserBase
    {
        public abstract int Id { get; set; }
        public abstract string Name { get; set; }
        public abstract string Email { get; set; }
    }

    public abstract class UserBase<TUser> : UserBase where TUser : UserBase
    {
        public static TUser FindByName_Broken(DataContext db, string name)
        {
            return db.GetTable<TUser>().FirstOrDefault(u => u.Name == name);
        }

        public static TUser FindByName_Works(DataContext db, string name)
        {
            return db.GetTable<TUser>().FirstOrDefault(u => u.Name == name && 1 == 1);
        }

        public static TUser FindByNameEmail_Works(DataContext db, string name, string email)
        {
            return db.GetTable<TUser>().FirstOrDefault(u => u.Name == name || u.Email == email);
        }
    }
}

These classes are referenced in another assembly "Main", like so:

namespace TestLinq2Sql
{
    partial class User : TestLinq2Sql.Shared.UserBase<User>
    {

    }
}

The DBML file is located in the "Main" assembly, as well.

When calling User.FindByName_Broken(db, "test"), an exception is thrown:

System.InvalidOperationException: Class member UserBase.Name is unmapped.

However, the other two base static methods work.

Furthermore, the SQL generated by calling User.FindByName_Works(db, "test") is what we were hoping for in the broken call:

SELECT TOP (1) [t0].[Id], [t0].[Name], [t0].[Email]
FROM [dbo].[Users] AS [t0]
WHERE [t0].[Name] = @p0
-- @p0: Input NVarChar (Size = 4; Prec = 0; Scale = 0) [test]

While I am willing to use this 1 == 1 "hack" for single predicate queries, is there a better way of sharing LINQ to SQL-aware code in a base/shared/core assembly?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

疑心病 2024-08-01 05:45:50

我过去多次遇到这个问题,因为我们公司使用的框架中有类似的架构。 您可能已经注意到,如果使用声明式 LINQ 查询,则不会遇到此问题。 例如,以下代码将起作用:

return (from i in db.GetTable<TUser>() where i.Name = "Something").FirstOrDefault();

但是,由于我们使用的是动态过滤器表达式,因此我们无法使用此方法。 另一种解决方案是使用类似这样的方法:

return db.GetTable<TUser>().Select(i => i).Where(i => i.Name == "Something").SingleOrDefault();

该解决方案解决了我们的问题,因为我们可以将“.Select(i => i)”注入到几乎所有表达式的开头。 这将导致 Linq 引擎不查看映射的基类,并强制它查看实际的实体类并查找映射。

希望能帮助到你

I have encountered this problem many times in the past because we have a similar architecture in a framework that we use in our company. You may have noticed that if you use the declarative style LINQ queries you'll not encounter this problem. For example the following code will work:

return (from i in db.GetTable<TUser>() where i.Name = "Something").FirstOrDefault();

However, since we are using dynamic filter expressions we couldn't use this method. The alternative solution is to use something like this:

return db.GetTable<TUser>().Select(i => i).Where(i => i.Name == "Something").SingleOrDefault();

This solution solved our problem since we can inject a ".Select(i => i)" to the beginning of almost all expressions. This will cause the Linq engine not to look at the base class for the mappings and will force it to look at the actual entity class and find the mappings.

Hope it helps

予囚 2024-08-01 05:45:50

尝试在Where子句之前包含OfType

return _dbContext.GetTable().OfType().Where(表达式).ToList();

Try including OfType before Where clause

return _dbContext.GetTable<T>().OfType<T>().Where(expression).ToList();

高速公鹿 2024-08-01 05:45:50

我很幸运在共享程序集中定义数据类并在许多程序集中使用它们,而不是将许多程序集的数据类映射到共享协定。 使用示例命名空间,将自定义 DataContext 和共享数据类放入 TestLinq2Sql.Shared:

namespace TestLinq2Sql.Shared
{
    public class SharedContext : DataContext
    {
        public Table<User> Users;
        public SharedContext (string connectionString) : base(connectionString) { }
    }

    [Table(Name = "Users")]
    public class User
    {
        [Column(DbType = "Int NOT NULL IDENTITY", IsPrimaryKey=true, CanBeNull = false)]
        public int Id { get; set; }

        [Column(DbType = "nvarchar(40)", CanBeNull = false)]
        public string Name { get; set; }

        [Column(DbType = "nvarchar(100)", CanBeNull = false)]
        public string Email { get; set; }
    }
}

然后使用任何其他程序集中的 DataContext:

using (TestLinq2Sql.Shared.SharedContext shared = 
    new TestLinq2Sql.Shared.SharedContext(
        ConfigurationManager.ConnectionStrings["myConnString"].ConnectionString))
{
    var user = shared.Users.FirstOrDefault(u => u.Name == "test");
}  

I've had luck defining data classes in a shared assembly and consuming them in many assemblies versus mapping many assemblies' data classes to a shared contract. Using your example namespaces, put a custom DataContext and your shared data classes in TestLinq2Sql.Shared:

namespace TestLinq2Sql.Shared
{
    public class SharedContext : DataContext
    {
        public Table<User> Users;
        public SharedContext (string connectionString) : base(connectionString) { }
    }

    [Table(Name = "Users")]
    public class User
    {
        [Column(DbType = "Int NOT NULL IDENTITY", IsPrimaryKey=true, CanBeNull = false)]
        public int Id { get; set; }

        [Column(DbType = "nvarchar(40)", CanBeNull = false)]
        public string Name { get; set; }

        [Column(DbType = "nvarchar(100)", CanBeNull = false)]
        public string Email { get; set; }
    }
}

Then consume the DataContext from any other assembly:

using (TestLinq2Sql.Shared.SharedContext shared = 
    new TestLinq2Sql.Shared.SharedContext(
        ConfigurationManager.ConnectionStrings["myConnString"].ConnectionString))
{
    var user = shared.Users.FirstOrDefault(u => u.Name == "test");
}  
菊凝晚露 2024-08-01 05:45:50

这看起来像一个错误 - 我们在主键上特殊情况下进行本地查找,但看起来此代码路径没有正确获取元数据。

1=1 黑客意味着它会通过正常的数据库往返,但实际上应该提交一个错误......

This looks like a bug - we special case Single on a primary key to do a local lookup but it looks like this code path is not grabbing the metadata properly.

The 1=1 hack will mean it goes via a normal database round-trip but really a bug should be filed...

烛影斜 2024-08-01 05:45:50

Jarrod,你在这里问了几个问题,你能说得更具体一些吗? 也就是说,您只想知道为什么您的方法失败吗? 或者也许您想要一种在不同项目中使用数据对象的方法? 我假设您没有尝试使用 LINQ to SQL 作为数据库映射层,而是将其用作域模型? 在这种情况下,两个应用程序是否实现相同的域(业务流程、验证等)?

You're asking several questions here Jarrod, can you be more specific? That is, do you just want to know why your method fails? Or maybe you want a way of using data objects across different projects? I'm assuming you're not trying to use LINQ to SQL as a database mapping layer and that you are using it as a domain model? In which case, do both applications implement the same domain (business processes, validation, etc.)?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文